上篇博客算法整理——【回溯法简述】-CSDN博客简单介绍了回溯算法,并以一道基础例题作为示范展示回溯法。本篇将继续整理和分析更多回溯法解决的问题。
一、回溯算法中组合的去重
例题为40. 组合总和 II - 力扣(LeetCode),给定一个候选人编号的集合 candidates
和一个目标数 target
,找出 candidates
中所有可以使数字和为 target
的组合。candidates
中的每个数字在每个组合中只能使用一次。
本题给的候选集合里有重复的数,但是组合出的结果又不能重复,所以我们要去重。
一种去重方法是按照之前的回溯组合方法,得到所有的组合之后,用map或者别的对组合进行去重。这种做法会容易超时。
另一种就是在搜索的过程中进行去重。并且是在层上去重(树层去重),不是树枝去重。
首先需要两个全局变量,一维数组path和二位数组result(存放结果)。
按照回溯三部曲,首先要确定回溯函数的参数和返回值。参数需要候选集、目标和、目前的和、startindex、是否包含该元素的数组。函数为void backtracking(nums, targetsum, sum, startindex, used);
然后确认终止条件,sum>targetsum则return;sum==targetsum就把path存入result。
第三步是单层逻辑,重点是去重的逻辑,如果该元素和之前的元素相同并且前一个元素的used为0(回溯过的),则return(去除)。(前一个used为1为树枝重复,不用去除)
整体代码如下:
class Solution {
public:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& candidates, int target, int sum, int start, vector<bool>& used)
{
if(sum == target)
{
result.push_back(path);
return;
}
for(int i = start; i<candidates.size() && sum + candidates[i] <= target; i++)
{
if(i>0&&candidates[i] == candidates[i-1] && used[i-1] == false)
{
continue;
}
sum+=candidates[i];
path.push_back(candidates[i]);
used[i]=1;
backtracking(candidates,target,sum,i+1,used);
used[i] = 0;
sum-=candidates[i];
path.pop_back();
}
}
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
vector<bool> used(candidates.size(), false);
// 首先把给candidates排序,让其相同的元素都挨在一起。
sort(candidates.begin(), candidates.end());
backtracking(candidates, target, 0, 0, used);
return result;
}
};
二、回溯算法分割回文串
例题为131. 分割回文串 - 力扣(LeetCode),给你一个字符串 s
,请你将 s
分割成一些子串,使每个子串都是 回文串。返回 s
所有可能的分割方案。
切割问题可以理解为组合问题,如对abcdef进行切割,对于第一层,可以选择切割a,可以切割ab,可以切割abc等等等。
接着进行回溯三部曲,第一步确认函数参数和返回值。还是跟其他的回溯题目一样,我们需要一维数组path存切割后的子串,需要二维数组result存放结果。本题递归函数参数还需要startIndex,因为切割过的地方,不能重复切割,和组合问题也是保持一致的。
然后确定终止条件,如果起始位置大于s的大小则说明已经找到一组分割方案了,把path存入result。
最后确定单层逻辑,单层逻辑为判断这个子串是否为回文,不是则跳过,是则push进path。然后继续回溯。
整体代码如下:
class Solution {
public:
vector<vector<string>> result;
vector<string> path;
void backtracking(string s, int start)
{
if(start>=s.size())
{
result.push_back(path);
return;
}
for(int i = start; i<s.size(); i++)//切割[start,i]
{
if(isP(s,start,i))//[start,i]是否回文
{
string str = s.substr(start,i-start+1);//s.substr(pos, len)
path.push_back(str);
}
else
{
continue;
}
backtracking(s,i+1);
path.pop_back();
}
}
bool isP(const string s, int start,int end)
{
for(int p = start,q = end;p<q; p++,q--)
{
if(s[p]!=s[q])
{
return 0;
}
}
return 1;
}
vector<vector<string>> partition(string s) {
backtracking(s,0);
return result;
}
};
说明:本文为作者整理知识点用于复习巩固,参考了代码随想录的讲解,有问题可以联系作者欢迎讨论~