组合总和
- 力扣题目链接
- 组合问题需要startIndex定位,即使有重复选取也一样!
- 直接给出剪枝版答案:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& candidates,
int target, int sum, int index) {
if(sum>target) return;
if(sum==target) {result.push_back(path); return;}
// 如果 sum+candidates[i]>target 就终止遍历
for(int i=index; i<candidates.size() &&
sum+candidates[i]<=target; i++){
sum+=candidates[i];
path.push_back(candidates[i]);
// 关键点:不用i+1了,表示可以重复读取当前的数
backtracking(candidates, target, sum, i);
sum-=candidates[i];
path.pop_back();
}
}
vector<vector<int>> combinationSum(
vector<int>& candidates, int target) {
// 需要排序
sort(candidates.begin(), candidates.end());
backtracking(candidates, target, 0, 0);
return result;
}
组合总和II
- 力扣题目链接
- 如果按照一般的写法,会出现重复:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& candidates, int target,
int sum, int index){
if(target==sum) {result.push_back(path); return;}
for(int i=index; i<candidates.size()
&& sum+candidates[i]<=target; i++){
sum+=candidates[i];
path.push_back(candidates[i]);
backtracking(candidates, target, sum, i+1);
sum-=candidates[i];
path.pop_back();
}
}
vector<vector<int>> combinationSum2(
vector<int>& candidates, int target) {
sort(candidates.begin(), candidates.end());
backtracking(candidates, target, 0, 0);
return result;
}
- 正确写法(利用used数组):
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& candidates, int target,
int sum, int index, vector<bool>& used) {
if(target==sum) {result.push_back(path); return;}
for(int i=index; 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);
used[i]=false;
sum-=candidates[i];
path.pop_back();
}
}
vector<vector<int>> combinationSum2(
vector<int>& candidates, int target) {
sort(candidates.begin(), candidates.end());
vector<bool> used(candidates.size(), false);
backtracking(candidates, target, 0, 0, used);
return result;
}
- 只利用startIndex也可以通过:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& candidates, int target,
int sum, int index) {
if(target==sum) {result.push_back(path); return;}
for(int i=index; i<candidates.size()
&& sum+candidates[i]<=target; i++){
// 要对同一树层使用过的元素进行跳过
if(i>index && candidates[i]==candidates[i-1])
continue;
sum+=candidates[i];
path.push_back(candidates[i]);
backtracking(candidates, target, sum, i+1);
sum-=candidates[i];
path.pop_back();
}
}
vector<vector<int>> combinationSum2(
vector<int>& candidates, int target) {
sort(candidates.begin(), candidates.end());
backtracking(candidates, target, 0, 0);
return result;
}
分割回文串
- 力扣题目链接
- 我们遇到的第一个分割问题,但切割问题也是组合问题!
- 回溯+判断回文(注意substr()函数的用法)
vector<vector<string>> result;
vector<string> path; // 放已经回文的子串
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;
}
void backtracking(const string& s, int index){
if(index>=s.size()) {result.push_back(path); return;}
for(int i=index; i<s.size(); i++) {
if(isPalindrome(s, index, i)) { //是回文子串
// 获取[startIndex,i]在s中的子串
string str=s.substr(index, i-index+1);
path.push_back(str);
} else continue; //不需要回溯
backtracking(s, i+1);
path.pop_back();
}
}
vector<vector<string>> partition(string s) {
backtracking(s, 0);
return result;
}
- 动态规划提前算好(提高版):
vector<vector<string>> result;
vector<string> path; // 放已经回文的子串
vector<vector<bool>> isPalindrome; // 放事先计算好的回文子串结果
void computePalindrome(const string& s) {
// isPalindrome[i][j]代表s[i:j](双边包括)是否是回文字串
isPalindrome.resize(s.size(), vector<bool>(s.size(), false));
for (int i=s.size()-1; i>=0; i--)
// 需要倒序计算, 保证在i行时, i+1行已经计算好了
for(int j=i; j<s.size(); j++) {
if(j==i) isPalindrome[i][j]=true;
else if(j-i==1) isPalindrome[i][j]=(s[i]==s[j]);
// 动态规划核心
else isPalindrome[i][j]=
(s[i]==s[j] && isPalindrome[i+1][j-1]);
}
}
void backtracking(const string& s, int index){
if(index>=s.size()) {result.push_back(path); return;}
for(int i=index; i<s.size(); i++) {
if(isPalindrome[index][i]){ //是回文子串
// 获取[startIndex,i]在s中的子串
string str=s.substr(index, i-index+1);
path.push_back(str);
} else continue; //不需要回溯
backtracking(s, i+1);
path.pop_back();
}
}
vector<vector<string>> partition(string s) {
computePalindrome(s);
backtracking(s, 0);
return result;
}
- 总的来说,是一道比较困难的模板题,需要认真吸收!
复原IP地址
- 同上题一样,判断合法性单独写,回溯步骤基本相同,只是这题确定了点数
- 注意掌握insert()和erase()函数的用法!
vector<string> result;
bool isValid(const string& s, int start, int end){
if(start>end || end-start>2) return false;
// 0开头的数字不合法
if(s[start]=='0' && start!=end) return false;
int num=0;
for(int i=start; i<=end; i++){
if(s[i]>'9' || s[i]<'0') return false;
num = num*10 + (s[i]-'0');
if(num>255) return false;
}
return true;
}
void backtracking(string& s, int start, int pointNum){
if(pointNum==3 && isValid(s, start, s.size()-1)) {
result.push_back(s); return;
}
for(int i=start; i<s.size() && isValid(s, start, i); i++){
s.insert(s.begin()+i+1, '.'); // 在i后插入
pointNum++;
backtracking(s, i+2, pointNum);
pointNum--;
s.erase(s.begin()+i+1);
}
}
vector<string> restoreIpAddresses(string s) {
// 又来一次剪枝优化
if(s.size()<4 || s.size()>12) return result;
backtracking(s, 0, 0);
return result;
}
子集
- 力扣题目链接
- 同上面题目的区别是,先push_back
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& nums, int start){
result.push_back(path);
if(start>=nums.size()) return;
for(int i=start; i<nums.size(); i++){
path.push_back(nums[i]);
backtracking(nums, i+1);
path.pop_back();
}
}
vector<vector<int>> subsets(vector<int>& nums) {
backtracking(nums, 0);
return result;
}
参考资料
[1] 代码随想录
[2] Leetcode题解