代码随想录算法训练营DAY27|C++回溯算法Part.3|39.组合总和、40.组合总和II、组合问题小总结、131.分割回文串

文章目录

  • 39.组合总和
    • 思路
    • 伪代码实现
    • 剪枝优化
    • CPP代码
      • 普通版本
      • 剪枝版本
  • 40.组合总和II
    • 思路
    • 伪代码
    • CPP代码
    • 不采用used数组的去重方法
  • 组合问题小总结
  • 131.分割回文串
    • 思路
    • 伪代码
    • CPP代码

39.组合总和

力扣题目链接

文章讲解:39.组合总和

视频讲解:Leetcode:39. 组合总和讲解

状态:强烈建议回看本篇文章内容组合总和III、电话号码的字母组合。基本就是沿用了这两门道题的主要思想:单层递归中算加法、index控制循环的深度。

思路

伪代码实现

  • 递归函数参数

    • 两个全局变量:result存放结果集,数组path存放符合条件的结果
    • 定义int型的sum变量来统计单一结果path里的总和,其实这个sum也可以不用,用target做相应的减法就可以了,最后如何target==0就说明找到符合的结果了,但为了代码逻辑清晰,依然用了sum
    • 使用startIndex控制for循环的起始位置。
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& candidates, int target, int sum, int startIndex)
    
  • 递归终止条件

从叶子结点可以看出,终止就是这两种情况,因为我们递归的纵向深度是动态的、不确定的。

if (sum > target){return ;
}
if (sum == target){result.push_back(path);return ;
}
  • 单层搜索的逻辑

单层for循环依然从startIndex开始,搜索candidates集合。但是一定要注意,从树形结构可以看出,我们本题元素是可以重复选取的,那么如何实现呢?最关键的代码就是:

backtracking(candidates, target, sum, i);

关键点:不用i+1了,表示可以重复读取当前的数

for (int i = startIndex; i < candidates.size(); i++){sum += candidates[i];path.push_back(candidates[i]);backtracking(candidates, target, sum, i);sum -= candidates[i];path.pop_back();
}

剪枝优化

本题中,对于sum已经大于target的情况,其实是依然进入了下一层递归,只是下一层递归结束判断的时候,会判断sum > target的话就返回。其实如果已经知道下一层sum会大于target的话,完全没有必要进入下一层递归,那么如果能够保证candidates数组是递增的就好了。

对总集合排序之后,如果下一层的sum(就是本层的 sum + candidates[i])已经大于target,就可以结束本轮for循环的遍历

所以我们对for循环的剪枝如下:

for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++)

CPP代码

普通版本

class Solution {
private:vector<vector<int>> result;vector<int> path;void backtracking(vector<int>& candidates, int target, int sum, int startIndex) {if (sum > target) {return;}if (sum == target) {result.push_back(path);return;}for (int i = startIndex; i < candidates.size(); i++) {sum += candidates[i];path.push_back(candidates[i]);backtracking(candidates, target, sum, i); // 不用i+1了,表示可以重复读取当前的数sum -= candidates[i];path.pop_back();}}
public:vector<vector<int>> combinationSum(vector<int>& candidates, int target) {result.clear();path.clear();backtracking(candidates, target, 0, 0);return result;}
};

剪枝版本

class Solution {
private:vector<vector<int>> result;vector<int> path;void backtracking(vector<int>& candidates, int target, int sum, int startIndex) {if (sum == target) {result.push_back(path);return;}// 如果 sum + candidates[i] > target 就终止遍历for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {sum += candidates[i];path.push_back(candidates[i]);backtracking(candidates, target, sum, i);sum -= candidates[i];path.pop_back();}}
public:vector<vector<int>> combinationSum(vector<int>& candidates, int target) {result.clear();path.clear();sort(candidates.begin(), candidates.end()); // 需要排序backtracking(candidates, target, 0, 0);return result;}
};

40.组合总和II

力扣题目链接

文章讲解:40.组合总和II

视频讲解:回溯算法中的去重,树层去重树枝去重,你弄清楚了没?| LeetCode:40.组合总和II

状态:本题最重要的——集合有重复元素,但是不能有重复的组合。首先要注意的是set、map驱虫是很容易潮湿的,只能在搜索的过程中就去掉重复组合。

这里点名两个重要的概念:树枝去重树层去重

思路

这里我们来理解本题中的去重

都知道组合问题可以抽象为树形结构,那么“使用过”在这个树形结构上是有两个维度的,一个维度是同一树枝上使用过,一个维度是同一树层上使用过。没有理解这两个层面上的“使用过” 是造成大家没有彻底理解去重的根本原因。

首先,同一树层使用会造成重复,同一树枝使用也会造成重复。而题目要求是,组合内可以重复,但两个组合不能相同。我们结合图片来看:

总而言之,我们要去重的是同一树层上的“使用过”,同一树枝上的都是一个组合里的元素,不用去重

强调一下,树层去重的话,需要对数组排序!

伪代码

  • 递归函数参数:与39.组合总和套路相同,此题还需要加一个bool型数组used,用来记录同一树枝上的元素是否使用过,这个集合去重的重任就是used来完成的。
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& candidates, int target, int sum, int startIndex, vector<bool>& used)
  • 终止递归条件
if (sum > target){	//如果执行剪枝操作,该步骤可以省略return ;
}
if (sum == target){result.push_back(path);return ;
}
  • 单层搜索的逻辑:

    • 本流程中最终要的就是去重“同一树层上的元素被使用过”。首先我们明确,同树层,我们用bool used来标识我们取的哪一个数,然后同树枝bool used来标识被取过的数。如下图所示:
      • used[i-1] == true, 说明同一树枝candidates[i-1]被使用过,说明现在应该进入下一层递归,取一下个数。。
      • Used[i-1] == false, 说明同一树层candidates[i-1]被使用过,表示了当前取的candidates[i]是从candidates[i-1]回溯过来的,是树层上的。
for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= traget; i++){// used[i - 1] == true,说明同一树枝candidates[i - 1]使用过// used[i - 1] == false,说明同一树层candidates[i - 1]使用过// 要对同一树层使用过的元素进行跳过if (i > 0 && candidate[i] == candidates[i-1] && used[i-1] == false){continue;}sum += candidates[i];path.push_back(candidates[i]);used[i] = true;backtracking(candidates, target, sum, i + 1, used);used[i] = false;sum -= candidates[i];path.pop_back();
}

说了这么多,其实used最主要的作用就是当前的遍历到底是在树枝上,还是在树层上

CPP代码

class Solution {
private:vector<vector<int>> result;vector<int> path;void backtracking(vector<int>& candidates, int target, int sum, int startIndex, vector<bool>& used) {if (sum == target) {result.push_back(path);return;}for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {// used[i - 1] == true,说明同一树枝candidates[i - 1]使用过// used[i - 1] == false,说明同一树层candidates[i - 1]使用过// 要对同一树层使用过的元素进行跳过if (i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false) {continue;}sum += candidates[i];path.push_back(candidates[i]);used[i] = true;backtracking(candidates, target, sum, i + 1, used); // 和39.组合总和的区别1,这里是i+1,每个数字在每个组合中只能使用一次used[i] = false;sum -= candidates[i];path.pop_back();}}public:vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {vector<bool> used(candidates.size(), false);path.clear();result.clear();// 首先把给candidates排序,让其相同的元素都挨在一起。sort(candidates.begin(), candidates.end());backtracking(candidates, target, 0, 0, used);return result;}
};

不采用used数组的去重方法

class Solution {
private:vector<vector<int>> result;vector<int> path;void backtracking(vector<int>& candidates, int target, int sum, int startIndex) {if (sum == target) {result.push_back(path);return;}for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {// 要对同一树层使用过的元素进行跳过//startIndex代表这一层中首次尝试的元素,//只有当'i>index'时,才说明我们在同一树层中考虑是否使用重复元素if (i > startIndex && candidates[i] == candidates[i - 1]) {continue;}sum += candidates[i];path.push_back(candidates[i]);backtracking(candidates, target, sum, i + 1); // 和39.组合总和的区别1,这里是i+1,每个数字在每个组合中只能使用一次sum -= candidates[i];path.pop_back();}}public:vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {path.clear();result.clear();// 首先把给candidates排序,让其相同的元素都挨在一起。sort(candidates.begin(), candidates.end());backtracking(candidates, target, 0, 0);return result;}
};

组合问题小总结

  • 什么时候要用startIndex来控制for循环,什么时候不用呢?

如果是一个集合来求组合的话,就需要用startIndex,例如:77.组合,216.组合总和III

如果是多个集合取组合,各个集合之间相互不影响,那么就用startIndex,例如:17.电话号码的字母组合。

131.分割回文串

力扣题目链接

文章讲解:131.分割回文串

视频讲解:131.分割回文串

状态:毫无想法,树形结构都画不出来

本题最主要是两个关键问题:

  1. 切割问题,有不同的切割方式
  2. 判断回文

思路

我们来分析一下切割,其实切割问题类似组合问题

例如对于字符串abcdef:

  • 组合问题:选取一个a之后,在bcdef中再去选取第二个,选取b之后在cdef中再选取第三个…。
  • 切割问题:切割一个a之后,在bcdef中再去切割第二段,切割b之后在cdef中再切割第三段…。

现在我们再来回答一下上文中的问题:

  1. 我们树形结构的层第一次切割,截取第一个字母,第二次切割截取前两个字母
  2. 我们对于树形结构的深就是每次判断一下是否为回文即可。

树形结构如图:递归用来总想遍历,for循环用来横向遍历。

伪代码

  • 递归函数参数

全局变量数组path存放切割后回文的子串,二维数组result存放结果集。 (这两个参数可以放到函数参数里)

本题递归函数参数还需要startIndex,因为切割过的地方,不能重复切割,和组合问题也是保持一致的。

vector<vector<string>> result;
vector<string> path; // 放已经回文的子串
void backtracking (const string& s, int startIndex) {
  • 递归终止条件

红色线条为切割线,每次切割完都表示找到了一种切割方法,都进行一次回文子串的判断,

void backtracking(const string& s, int startIndex){//如果起始位置已经大于s的大小,说明已经找到了一组分割方案if (startIndex >= s.size()){result.push_back(path);return;}
}
  • 单层搜索的逻辑

递归循环中如何截取子串

for (int i = startIndex; i < s.size(); i++){if (isPalindrom(s, startIndex, i)){//是回文子串//获取[startIndex, i]在s中的子串string str = s.substr(startIndex, i - startIndex + 1);path.push_back(str);}else{continue;//不是回文就直接跳过}backtracking(s, i + 1); //寻找i+1为起始位置的子串path.pop_back();	//回溯过程,染出本次已经调价的子串
}

注意切割过的位置,不能重复切割,所以,backtracking(s, i + 1); 传入下一层的起始位置为i + 1


判断回文子串

最后我们看一下回文子串要如何判断了,判断一个字符串是否是回文。

可以使用双指针法,一个指针从前向后,一个指针从后向前,如果前后指针所指向的元素是相等的,就是回文字符串了。

bool isPalindrome(const string& s, int start, int end){for (int i = start, j = end; i < j; i++, j--){if (s[i] != s[j]){return false;}}return true;
}

CPP代码

class Solution {
private:vector<vector<string>> result;vector<string> path; // 放已经回文的子串void backtracking (const string& s, int startIndex) {// 如果起始位置已经大于s的大小,说明已经找到了一组分割方案了if (startIndex >= s.size()) {result.push_back(path);return;}for (int i = startIndex; i < s.size(); i++) {if (isPalindrome(s, startIndex, i)) {   // 是回文子串// 获取[startIndex,i]在s中的子串string str = s.substr(startIndex, i - startIndex + 1);path.push_back(str);} else {                                // 不是回文,跳过continue;}backtracking(s, i + 1); // 寻找i+1为起始位置的子串path.pop_back(); // 回溯过程,弹出本次已经添加的子串}}bool isPalindrome(const string& s, int start, int end) {for (int i = start, j = end; i < j; i++, j--) {if (s[i] != s[j]) {return false;}}return true;}
public:vector<vector<string>> partition(string s) {result.clear();path.clear();backtracking(s, 0);return result;}
};

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据结构——顺序表——数组元素和与数字和的绝对差

数据结构——顺序表——数组元素和与数字和的绝对差 class Solution { public:int differenceOfSum(vector<int>& nums) {int a 0;int b 0;for(int i 0; i < nums.size(); i){a a nums[i];while(nums[i]){b b (nums[i] % 10);nums[i] nums[i] / 10;}}retu…

常见的Linux命令

linux操作系统 ctrl鼠标滚动 放大缩小字体 cd /目录进入目录下 ls查看当前目录下的所有内容 tar -zxvf 压缩包名字 对压缩包进行解压 sync将数据由内存同步到硬盘上 shutdown关机指令 shutdown -h 10 /10 表示十分钟后关机 shutdown -h now 表示现在关机 shutdown -h…

OpenCV4.9图像金字塔

目标 在本教程中&#xff0c;您将学习如何&#xff1a; 使用 OpenCV 函数 pyrUp()和 pyrDown()对给定图像进行下采样或上采样。 理论 注意 下面的解释属于 Bradski 和 Kaehler 的 Learning OpenCV 一书。 通常&#xff0c;我们需要将图像转换为与原始图像不同的大小。为此…

springboot上传模块到私服,再用pom引用下来

有时候要做一个公司的公共服务模块。不能说大家都直接把代码粘贴进去&#xff0c;因为会需要维护很多份&#xff1b;这样就剩下两个方式了。 方式一&#xff1a;自己独立部署一个公共服务的服务&#xff0c;全公司都调用&#xff0c;通过http、rpc或者grpc的方式&#xff0c;这…

用 Python 写 3D 游戏,太赞了!

vizard介绍 Vizard是一款虚拟现实开发平台软件&#xff0c;从开发至今已走过十个年头。它基于C/C&#xff0c;运用新近OpenGL拓展模块开发出的高性能图形引擎。当运用Python语言执行开发时&#xff0c;Vizard同时自动将编写的程式转换为字节码抽象层(LAXMI)&#xff0c;进而运…

msf后门流量分析

前言 分析msf后门的流量主要是分析其数据包的特征&#xff0c;然后msf后门有三种类型。分别是tcp、http、https&#xff0c;这里就说一下这三种类型的数据包的特征&#xff0c;其实也是比较简单的。还有一点&#xff0c;在实验之前记得把病毒防护关了&#xff0c;以防拦截流量…

The 0-1 Knapsack Problem KNAPSACK

Problem Let U {u1, u2, . . . , un} be a set of n items to be packed in a knapsack of size C. For 1 ≤ j ≤ n, let sj and vj be the size and value of the jth item, respectively. Here C and sj, vj , 1 ≤ j ≤ n, are all positive integers. Each item should e…

水库之大坝安全监测系统解决方案

一、系统介绍 水库之大坝安全监测系统主要包括渗流监测系统、流量监测系统、雨量监测系统、沉降监测系统组成。每一个监测系统由监测仪器及自动化数据采集装置&#xff08;内置通信装置、防雷设备&#xff09;、附件&#xff08;电缆、通信线路、电源线路&#xff09;等组成&a…

node.js服务器静态资源处理

前言&#xff1a;node.js服务器动态资源处理见 http://t.csdnimg.cn/9D8WN 一、什么是node.js服务器静态资源&#xff1f; 静态资源服务器指的是不会被服务器的动态运行所改变或者生成的文件. 它最初在服务器运行之前是什么样子, 到服务器结束运行时, 它还是那个样子. 比如平…

github 双因素验证

环境 华为手机 下载app 华为应用市场下载 输入对应验证码&#xff0c;然后一路下一步即可 联系方式 手机&#xff1a;13822161573 微信&#xff1a;txsolarterms QQ&#xff1a;419396409

腾讯客户端开发实习一面

听说腾讯25年5000offer&#xff0c;我就去了...投完简历&#xff0c;当天晚上做完测评&#xff0c;第二天下午打电话约了第三天面试&#xff0c;额流程很快&#xff0c;快到第三天就寄了... 写在这里做个记录&#xff0c;也可以给学习学妹们经验&#xff0c;文末也有大厂面经合…

人工智能研究生前置知识—扩展程序库Pandas

人工智能研究生前置知识—扩展程序库Pandas pandas简介 Pandas 的主要数据结构是 Series &#xff08;一维数据&#xff09;与 DataFrame&#xff08;二维数据&#xff09;。Pandas 广泛应用在学术、金融、统计学等各个数据分析领域。 pandas的官网&#xff1a;https://pandas.…