算法练习第21天|216.组合总和|||、17.电话号码的字母组合

216.组合总和 III

216. 组合总和 III - 力扣(LeetCode)icon-default.png?t=N7T8https://leetcode.cn/problems/combination-sum-iii/

题目描述:

找出所有相加之和为 n 的 k 个数的组合,且满足下列条件:

  • 只使用数字1到9
  • 每个数字 最多使用一次 

返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。

示例 1:

输入: k = 3, n = 7
输出: [[1,2,4]]
解释:
1 + 2 + 4 = 7
没有其他符合的组合了。

示例 2:

输入: k = 3, n = 9
输出: [[1,2,6], [1,3,5], [2,3,4]]
解释:
1 + 2 + 6 = 9
1 + 3 + 5 = 9
2 + 3 + 4 = 9
没有其他符合的组合了。

示例 3:

输入: k = 4, n = 1
输出: []
解释: 不存在有效的组合。
在[1,9]范围内使用4个不同的数字,我们可以得到的最小和是1+2+3+4 = 10,因为10 > 1,没有有效的组合。

思路分析:

这道题是在77.组合的基础上做了改进,相关回溯解法可以参考上一篇博文:

算法练习第20天|回溯算法 77.组合问题 257. 二叉树的所有路径-CSDN博客

和77组合题目一样,本题抽象成二叉树的逻辑如下(图片来自卡哥的代码随想录): 

  使用回溯的解法,我们依然要使用path和result两个vector,一个用于记录遍历路径,另一个用于记录满足条件的结果:

vector<vector<int>> result;  //满足条件的结果
vector<int> path;  //当前路径

下面我们回顾一下上一篇博文中所讲的回溯三部曲

第一步:确认回溯函数的参数和返回值。

void backtracking(参数)
{
}

一般来说,回溯函数的返回值类型为void,至于参数,为了表达方便,我们定义了目标和targetSum(即题目中的n)、k、遍历到当前路径的和sum、以及每一层回溯的开始索引startIndex。具体代码如下:

// int targetSum; //目标和
// int k;  //k个元素
// int sum;  //当前path记录的元素的和
// int startIndex;  //开始的索引
//回溯第一步:确认回溯函数的参数以及返回值
void backTracking(int targetSum, int k, int sum, int startIndex)
{
}

第二步:确认回溯的终止条件。

if (终止条件) {
    存放结果;
    return;
}

什么时候本次回溯终止?那就是我们成功的找到了一组数据,里面有k个元素,且它们的和为n。那么终止条件就是 path.size() == k && sum == targetSum。找到这一组数据则需要将其记录在结果result中,然后return。具体代码如下:

if(path.size() == k && sum == targetSum)
{
    result.push_back(path);
    return;
}

但其实更严谨的逻辑应该是, 先检查是否遍历了k个元素,即path的size是否为k。如果遍历了k个元素,则判断它们的和sum是否等于targetSum,如果相等,则记录该组数据;不相等则不记录。但是不论是否相等,此时已经是遍历了k个元素,已经不能再继续遍历了,所以直接return,结束掉本次的回溯。代码如下:

if(path.size() == k )
{
     if(sum == targetSum)
          result.push_back(path);
     return;
}

如果不满足上述的第一个条件,则说明还没有遍历到k个元素,应该执行单层回溯的具体逻辑。

第三步:确认单层回溯的遍历过程

for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
    处理节点;
    backtracking(路径,选择列表); // 递归
    回溯,撤销处理结果
}

这一过程要做哪些事情?

首先,从startIndex开始遍历元素,将当前元素加到sum中,并用path加以记录;然后就可以从startIndex+1的位置进行递归了,递归之后,该记录的结果会进行记录。然后要进行抽象成的二叉树遍历过程回溯过程,即当前节点退出sum和path,具体代码如下:

for(int i = startIndex; i<=9; i++)
        {
            //处理节点
            sum += i;
            path.push_back(i);

            //递归
            backTracking(targetSum, k, sum, i+1);  //注意将i+1调整为startIndex

            //回溯
            sum -= i;
            path.pop_back(i);
        }

完整代码:

class Solution {
public:
    vector<vector<int>> result;  //满足条件的结果
    vector<int> path;  //当前路径
    // int targetSum; //目标和
    // int k;  //k个元素
    // int sum;  //当前path记录的元素的和
    // int startIndex;  //开始的索引

    //回溯第一步:确认回溯函数的参数以及返回值
    void backTracking(int targetSum, int k, int sum, int startIndex)
    {
        //回溯第二步:确认回溯函数的终止条件
        //什么时候终止此次回溯?那就是找到了一组数(k个数),且它们的和为n
        if(path.size() == k )
        {
            if(sum == targetSum)
                result.push_back(path);
            return;
        }

        //回溯第三步:确认单层回溯的遍历过程
        //单层回溯要做那些事情?遍历!从startIndex开始遍历
        for(int i = startIndex; i<=9; i++)
        {
            //处理节点
            sum += i;
            path.push_back(i);

            //递归
            backTracking(targetSum, k, sum, i+1);  //注意将i+1调整为startIndex

            //回溯
            sum -= i;
            path.pop_back(i);
        }


    }


    vector<vector<int>> combinationSum3(int k, int n) {
        backTracking(n,k,0,1);
        return result;
    }
};

进一步剪枝处理优化代码:

剪枝其实就是在二叉树遍历逻辑的基础上,去点一些明显没有必要的遍历路径,如下图所示:

当遍历元素的和大于题目要求的n时,其实已经没有意义了。 

那么剪枝的地方可以放在递归函数开始的地方,剪枝代码如下:

if (sum > targetSum) { // 剪枝操作
    return;
}

 除此之外,遍历过程也可以再剪枝:

接下来看一下优化过程如下:

  1. 已经选择的元素个数:path.size();

  2. 所需需要的元素个数为: k - path.size();

  3. 列表中剩余元素(n-i) >= 所需需要的元素个数(k - path.size())

  4. 在集合n中至多要从该起始位置 : i <= n - (k - path.size()) + 1,开始遍历,往后找还能找到k个数。

代码如下:

class Solution {
private:
    vector<vector<int>> result; // 存放结果集
    vector<int> path; // 符合条件的结果
    void backtracking(int targetSum, int k, int sum, int startIndex) {
        if (sum > targetSum) { // 剪枝操作
            return; 
        }
        if (path.size() == k) {
            if (sum == targetSum) result.push_back(path);
            return; // 如果path.size() == k 但sum != targetSum 直接返回
        }
        for (int i = startIndex; i <= 9 - (k - path.size()) + 1; i++) { // 剪枝
            sum += i; // 处理
            path.push_back(i); // 处理
            backtracking(targetSum, k, sum, i + 1); // 注意i+1调整startIndex
            sum -= i; // 回溯
            path.pop_back(); // 回溯
        }
    }

public:
    vector<vector<int>> combinationSum3(int k, int n) {
        result.clear(); // 可以不加
        path.clear();   // 可以不加
        backtracking(n, k, 0, 1);
        return result;
    }
};

17.电话号码的字母组合

17. 电话号码的字母组合 - 力扣(LeetCode)icon-default.png?t=N7T8https://leetcode.cn/problems/letter-combinations-of-a-phone-number/description/题目描述:

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

示例 1:

输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]

示例 2:

输入:digits = ""
输出:[]

示例 3:

输入:digits = "2"
输出:["a","b","c"]

 

数字和字母如何映射

可以使用map或者定义一个二维数组,例如:string letterMap[10],来做映射,我这里定义一个二维数组,代码如下:

    const string letterMap[10] = {
        "", //0
        "", //1
        "abc", //2
        "def", //3
        "ghi", //4
        "jkl", //5
        "mno", //6
        "pqrs", //7
        "tuv", //8
        "wxyz", //9
    };

回溯法来解决n个for循环的问题

例如:输入:"23",抽象为树形结构,如图所示:

17. 电话号码的字母组合

 这就和之前的77组合以及216组合III有了相似之处。然后只用回溯三部曲。

回溯三部曲:

  • 确定回溯函数参数

首先需要一个字符串s来收集叶子节点的结果,然后用一个字符串数组result保存起来,这两个变量我依然定义为全局。

再来看参数,参数指定是有题目中给的string digits,然后还要有一个参数就是int型的index。这个index用来表示遍历到digits中的第几个数字了。与之前的startIndex不一样。

    vector<string> result;
    string path;
    //回溯第一步
    void backTracking(const string &digits, int index)
    {
    }
  •  确认回溯的终止条件

index表示遍历到了digits的第几个数字,其初始值为0,遍历过一个数字后就会+1,那么当index等于digits.size()时,表明遍历完毕。代码如下:

if (index == digits.size()) {
    result.push_back(s);
    return;
}
  • 确认单层回溯逻辑

首先,要根据index把数字对应的字符串取出来,然后遍历该字符串,将字母加入到路径path中。添加完一个字母(对应模板中的处理节点),则 该去执行递归,index+1,然后回溯:

int digit = digits[index] - '0';        // 将index指向的数字转为int
string letters = letterMap[digit];      // 取数字对应的字符集
for (int i = 0; i < letters.size(); i++) {
    s.push_back(letters[i]);            // 处理
    backtracking(digits, index + 1);    // 递归,注意index+1,一下层要处理下一个数字了
    s.pop_back();                       // 回溯
}

整体代码如下: 

class Solution {
private:
    const string letterMap[10] = {
        "", //0
        "", //1
        "abc", //2
        "def", //3
        "ghi", //4
        "jkl", //5
        "mno", //6
        "pqrs", //7
        "tuv", //8
        "wxyz", //9
    };

public:
    vector<string> result;
    string path;
    //回溯第一步
    void backTracking(const string digits, int index)
    {
        //回溯第二步:确认回溯终止条件
        if(index == digits.size())
        {
            result.push_back(path);
            return ;
        }

        //回溯第三步:确认单层回溯逻辑操作
        int num = digits[index] - '0';  //获取对应的数字
        string letters = letterMap[num];  //获取该数字对应的字母 
        for(int i = 0 ; i < letters.size(); i++)
        {
            path.push_back(letters[i]);  //加入一个字母
            backTracking(digits, index+1);  //找下一个数字对应的字母
            path.pop_back();  //回溯
        }

    }
    vector<string> letterCombinations(string digits) {
        if(digits.empty())
            return result;
        backTracking(digits, 0);
        return result;
    }
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/625227.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ICode国际青少年编程竞赛- Python-5级训练场-带参数函数

ICode国际青少年编程竞赛- Python-5级训练场-带参数函数 1、 def get_item(a):Dev.step(a)Dev.step(-a) get_item(4) Spaceship.step(2) get_item(2) Spaceship.step(3) get_item(5) Spaceship.step(2) get_item(3) Spaceship.step(3) get_item(4)2、 def get_item(a): D…

超链接a的应用

主要作用&#xff1a;从当前页面进行跳转 1.跳转到页面 <!-- 跳转到其他页面 --><a href"#" target"_blank">鸡你太美</a> <!-- 跳转到本地页面 --><a href"#" target"_self">鸡你太美</a> 2.跳转…

AI大事记(持续更新)

文章目录 前言 一、人工智能AI 1.基本概念 2.相关领域 2.1基础设施 2.2大模型 2.3大模型应用 二、大事记 2024年 2024-05-14 GPT-4o发布 2024-02-15 Sora发布 2023年 2023-03-14 GPT-4.0发布 2022年 2022-11-30 ChatGPT发布 总结 前言 2022年11月30日openai的…

jdk安装多个版本,但是java -version显示最早安装的版本,换掉Path或者JAVA_HOME不生效问题

问题一&#xff1a;当你的电脑上又多个jdk版本&#xff0c;如17 或者8时&#xff0c;使用命令行 java -version显示最早安装的&#xff0c;如下图所示&#xff1a;环境变量配置的17&#xff0c;但是命令行显示的是8。 原因&#xff1a;windows电脑装jdk17后 会在你的环境变量…

C for Graphic:遮罩显示(一)

模板缓冲一般用于遮罩渲染的功能&#xff0c;其原理很以前聊过&#xff08;模板缓冲原理&#xff09;&#xff0c;就不再啰嗦了。 现在实现一个功能&#xff1a;使用一个长方体&#xff08;或任意物体&#xff09;遮罩渲染对象&#xff08;比如一个球&#xff09;。 …

Linux下COOLFluiD源码编译安装及使用

目录 软件介绍 基本依赖 其它可选依赖 一、源码下载 二、解压缩&#xff08;通过Github下载zip压缩包格式&#xff09; 三、编译安装 3.1 依赖项-BOOST 3.2 依赖项-Parmetis 3.3 依赖项-PETSc 3.4 安装COOLFluiD 四、算例运行 软件介绍 COOLFluiD&#xff08;面向对象…

Autoware内容学习与初步探索(一)

0. 简介 之前作者主要是基于ROS2&#xff0c;CyberRT还有AutoSar等中间件完成搭建的。有一说一&#xff0c;这种从头开发当然有从头开发的好处&#xff0c;但是如果说绝大多数的公司还是基于现成的Apollo以及Autoware来完成的。这些现成的框架中也有很多非常好的方法。目前作者…

深度学习之激活函数——ReLU

ReLU 整流线性单元(ReLU)&#xff0c;全称Rectified linear unit&#xff0c;是现代神经网络中最常用的激活函数&#xff0c;大多数前馈神经网络都默认使用该激活函数。 函数表达式 f ( x ) m a x { 0 , x } f(x)max\{0,x\} f(x)max{0,x} 当 x < 0 x<0 x<0时&…

5月14(信息差)

&#x1f30d;字节携港大南大升级 LLaVA-NeXT&#xff1a;借 LLaMA-3 和 Qwen-1.5 脱胎换骨&#xff0c;轻松追平 GPT-4V Demo 链接&#xff1a;https://llava-next.lmms-lab.com/ &#x1f384;阿里巴巴开源的15个顶级Java项目 ✨ 欧洲在线订餐服务Takeaway.com&#xff1a…

数据结构与算法学习笔记十二-二叉树的顺序存储表示法和实现(C语言)

目录 前言 1.数组和结构体相关的一些知识 1.数组 2.结构体数组 3.递归遍历数组 2.二叉树的顺序存储表示法和实现 1.定义 2.初始化 3.先序遍历二叉树 4.中序遍历二叉树 5.后序遍历二叉树 6.完整代码 前言 二叉树的非递归的表示和实现。 1.数组和结构体相关的一些知…

第五课,输入函数、布尔类型、比较运算和if判断

一&#xff0c;输入函数input() 与输出函数print()相对应的&#xff0c;是输入函数input()&#xff0c;前者是把程序中的数据展示给外界&#xff08;比如电脑屏幕上&#xff09;&#xff0c;而后者是把外界&#xff08;比如键盘&#xff09;的数据输入进程序中 input()函数可…

秋招算法——背包模型——423采药问题——模板:背包问题

文章目录 题目描述思路分析实现代码分析总结 题目描述 思路分析 这里明显是使用背包问题&#xff0c;所以这里参考一下背包这个模板题的内容这个是朴素版的模板&#xff0c;没有经过代码的优化 #include <iostream> #include <algorithm>using namespace std;con…

字符串函数(二):strlen(求长度),strstr(查找子串),strtok(分割),strerror(打印错误信息)

字符串函数 一.strlen&#xff08;求字符串长度&#xff09;1.函数使用2.模拟实现&#xff08;三种方法&#xff09; 二.strstr&#xff08;字符串查找子串&#xff09;1.函数使用2.模拟实现 三.strtok&#xff08;字符串分割&#xff09;四.strerror&#xff0c;perror&#x…

24点游戏679

题目描述&#xff1a; 给定一个长度为4的整数数组 cards 。你有 4 张卡片&#xff0c;每张卡片上都包含一个范围在 [1,9] 的 数字。您应该使用运算符 [, -, *, /] 和括号 ( 和 ) 将这些卡片上的数字排 列成数学表达式&#xff0c;以获得值24。你须遵守以下规则: &#xff08;1&…

AI大模型日报#0514:OpenAI GPT-4o震撼发布、我是如何赢得GPT-4提示工程大赛冠军的

导读&#xff1a;欢迎阅读《AI大模型日报》&#xff0c;内容基于Python爬虫和LLM自动生成。目前采用“文心一言”生成了今日要点以及每条资讯的摘要。《AI大模型日报》今日要点&#xff1a;OpenAI在春季新品发布会上推出全能模型GPT-4o及桌面App&#xff0c;颠覆科技界。GPT-4o…

Pytorch学习-引言

Pytorch相关链接 Pytorch官方网站 https://pytorch.org/ Pytorch的Github仓库 https://github.com/pytorch/pytorch Pytorch论坛 https://discuss.pytorch.org/ Pytorch离线下载包链接 https://download.pytorch.org/whl/torch_stable.html Pytorch学习视频推荐链接 http://【…

C++类与对象基础探秘系列(二)

目录 类的6个默认成员函数 构造函数 构造函数的概念 构造函数的特性 析构函数 析构函数的概念 析构函数的特性 拷贝构造函数 拷贝构造函数的概念 拷贝构造函数的特性 赋值运算符重载 运算符重载 赋值运算符重载 const成员 const修饰类的成员函数 取地址及const取地址操作…

C++系统编程篇——Linux初识(系统安装、权限管理,权限设置)

(1)linux系统的安装 双系统---不推荐虚拟机centos镜像&#xff08;可以使用&#xff09;云服务器/轻量级云服务器&#xff08;强烈推荐&#xff09; ①云服务器&#xff08;用xshell连接&#xff09; ssh root公网IP 然后输入password ①添加用户&#xff1a; addus…

如何去掉试卷答案,并打印出来

实际上&#xff0c;针对试卷答案的问题&#xff0c;一个简单而高效的方法是使用图片编辑软件中的“消除笔”功能。只需将试卷拍摄成照片&#xff0c;然后通过这一功能&#xff0c;就可以轻松擦除答案。虽然这种方法可能需要一些时间和耐心&#xff0c;但它确实为我们提供了一个…