代码随想录算法训练营day23
代码随想录算法训练营
—day23
文章目录
- 代码随想录算法训练营
- 前言
- 一、39. 组合总和
- 二、40.组合总和II
- 三、131.分割回文串
- 总结
前言
今天是算法营的第23天,希望自己能够坚持下来!
今日任务:
● 39. 组合总和
● 40.组合总和II
● 131.分割回文串
一、39. 组合总和
题目链接
文章讲解
视频讲解
思路:
- 递归函数的参数和返回值:参数:整数数组 candidates,目标整数 target,总和sum,遍历的开始索引startIndex。
- 终止条件:本题对path的大小没有限制,只有总和的限制,当总和=target的时候结束,将path放入结果集。
- 单层递归的逻辑:纵向遍历可以重复使用数字,所以for循环中递归用的是i(若不重复则是i+1)。
- 去重:先将数组排序,如果sum+candidates[i]>target那么后面的元素都会大于target,所以for循环的终止条件可以加上这个,提前退出循环。
class Solution {
public:vector<int> path;vector<vector<int>> result;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); //这里因为同一个数字可以重复使用,所以用startIndex是isum -= candidates[i]; //回溯path.pop_back(); //回溯}return;}vector<vector<int>> combinationSum(vector<int>& candidates, int target) {path.clear();result.clear();sort(candidates.begin(), candidates.end()); //需要排序if (candidates.size() == 0) return result;backtracking(candidates, target, 0, 0);return result;}
};
二、40.组合总和II
题目链接
文章讲解
视频讲解
这道题目和39.组合总和 (opens new window)如下区别:
1.本题candidates 中的每个数字在每个组合中只能使用一次。
2.本题数组candidates的元素是有重复的,而39.组合总和 (opens new window)是无重复元素的数组candidates
最后本题和39.组合总和 (opens new window)要求一样,解集不能包含重复的组合。
本题的难点在于区别2中:集合(数组candidates)有重复元素,但还不能有重复的组合。
本题是多加了一个used数组,去记录纵向遍历时使用过的数字。
树层去重的话,需要对数组排序!
思路:
- 递归函数参数以及返回值:参数数组candidates,目标值target,总和sum,索引startIndex,标记使用过的数字数组used。
- 终止条件:sum == target
- 单层处理逻辑:当前元素与前一位元素相等,且是前一位元素没用过,也就是以当前元素(重复元素)开始寻找新的结果,跳过这次循环
代码如下:
class Solution {
public:vector<int> path;vector<vector<int>> result;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 ++) {//当前元素与前一位元素相等,且是前一位元素没用过,也就是以当前元素开始寻找新的结果,跳过这次循环if (i > 0 && candidates[i] == candidates[i-1] && used[i-1] == false) continue; //去重path.push_back(candidates[i]);sum += candidates[i];used[i] = true; //记录使用当前结果path用了那些元素backtracking(candidates, target, sum, i + 1, used); //candidates里的数字只能用一次,i+1sum -= candidates[i];path.pop_back();used[i] = false;}}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());if (candidates.size() == 0) return result;backtracking(candidates, target, 0, 0, used);return result;}
};
三、131.分割回文串
题目链接
文章讲解
视频讲解
跟组合问题类似,递归遍历每个字符串的位置去切割,每一种结果都是一种多个切割点的组合。只是每次切割都需要判断切割后是否为回文字符子串,是则放入path,继续切割,否则继续遍历寻找合适的切割点。
思路:
- 递归函数的参数:传入字符串s,遍历索引startIndex
- 终止条件:startIndex已经到达字符串尾端(将判断回文放在了for循环中,这里直接放入结果集)
- 单层递归的逻辑:[startIndex,i]就是本次截取的字符串,判断其是否回文,是则加入path中,否则continue去寻找合适的切割点。递归寻找i+1为起始位置的子串。
class Solution {
public:vector<string> path; //放已经回文的子串vector<vector<string>> result;void backtracking(string& s, int startIndex) {if (startIndex >= s.size()) {result.push_back(path);return;}for (int i = startIndex; i < s.size(); i++) {if (isPalindrome(s, startIndex, i)) { //判断[startIndex,i]之间的字符串是否为回文子串//是则截取出来,放入pathstring str = s.substr(startIndex, i - startIndex + 1); //substr是左闭右开区间所以+1path.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;}vector<vector<string>> partition(string s) {result.clear();path.clear();if (s.size() == 0) return result;backtracking(s, 0);return result;}
};
总结
1.今天第一次涉及到去重,去重又分树枝(纵向)去重和树层(横向)去重。可以使用used数组去标记使用过的元素。
2.分割问题跟组合问题类似,也是求不同分割点的组合。在for循环中判断是否符合切割要求。
明天继续加油!