39. 组合总和
本题和216. 组合总和 III非常相似(题解)。本题中单个组合中可以无限重复同一个数字,而216. 组合总和 III不可重复,因此在递归时传入下一个位置表示不重复,传入当前位置表示可重复。
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
n
)
O(n)
O(n)
// c++
class Solution {
public:
int sum = 0;
vector<int> cur;
void backtracking(vector<vector<int>> &result, const vector<int>& candidates, int left, int target){
if(sum == target){
result.emplace_back(cur);
return;
}
if(left >= candidates.size() || sum>target) return;
for(int i=left; i<candidates.size(); i++){
sum += candidates[i];
cur.emplace_back(candidates[i]);
backtracking(result, candidates, i, target);
cur.pop_back();
sum -= candidates[i];
}
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
vector<vector<int>> result;
backtracking(result, candidates, 0, target);
return result;
}
};
40. 组合总和 II
由于candidata中有重复数字,故本题允许单个组合内有重复数字(这里的重复数字是在candidate中不同下标相同的元素,不是同一个元素),但不允许有重复组合,也就是说,允许出现[1, 1, 2]的单个组合,但不允许[[1,1,2], [1,1,2]]或[[1,1,2],[1,2,1]]或[[1,1,2],[2,1,1]]这样相同组合出现两次。
若求出所有组合再用set去重会导致时空消耗大。所以需要在寻找答案时就去重。
先将candidate排序,使其中相同元素挨在一起。在回溯结束时进行去重,也就是下标后移。由于重复元素可能不止一个,所以去重用while而不是if。
在写代码的时候我考虑过在for循环开始时去重,这里会有一些问题,手动走一遍就清楚了:
for(int i=left; i<candidates.size(); i++){
// 去重
while(i>0 && candidates[i] == candidates[i-1]) i++;
sum += candidates[i];
cur.emplace_back(candidates[i]);
backtracking(result, candidates, i+1, target);
sum -= candidates[i];
cur.pop_back();
}
(这里用题目第一个样例举例)
candidates = [10,1,2,7,6,1,5], target = 8
排序过后, candidates = [1,1,2,5,6,7,10],调用回溯算法。
- left=0,i=0,不进入while循环,sum=1,cur = [1],进入递归。
- left=1,i=1,进入while循环,i=2,sum=3,cur=[1,2],再进下一层递归。
在这里就可以看出问题了,这种写法会跳过单个组合的重复元素,所以要将去重写在回溯结束之后。
所以针对这一问题进行改进,不难发现上面问题的出现是由于递归开始没有处理元素就跳过了,这样导致组合内的重复、重复的组合都被跳过,但其实要处理的是重复的组合而不是组合内的重复数字,所以将去重条件稍作修改。组合内重复就需要将元素放入组合,也就是将传入的第一个元素放入组合。在回溯结束后当前元素被弹出,当下一个元素和当前元素相同时,就会出现重复的组合,因此将上边的去重代码改为如下条件:
for(int i=left; i<candidates.size(); i++){
// 去重写法一 将第一个元素放入组合
while(i>left && candidates[i] == candidates[i-1]) i++;
// 去重写法二
// if(i>left && candidates[i] == candidate[i-1]) continue;
sum += candidates[i];
cur.emplace_back(candidates[i]);
backtracking(result, candidates, i+1, target);
sum -= candidates[i];
cur.pop_back();
}
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
n
)
O(n)
O(n)
完整代码如下:
// c++
class Solution {
public:
int sum = 0;
vector<int> cur;
void backtracking(vector<vector<int>> &result, const vector<int>& candidates, int left, int target){
if(sum == target){
result.emplace_back(cur);
return;
}
if(left >= candidates.size() || sum > target) return;
for(int i=left; i<candidates.size(); i++){
// 去重写法一
// if(i>left && candidates[i] == candidates[i-1]) continue;
// 去重写法二
// while(i>left && candidates[i] == candidates[i-1]) i++;
sum += candidates[i];
cur.emplace_back(candidates[i]);
backtracking(result, candidates, i+1, target);
sum -= candidates[i];
cur.pop_back();
// 去重写法三
while(i<candidates.size()-1 && candidates[i] == candidates[i+1]) i++;
}
}
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
vector<vector<int>> result;
sort(candidates.begin(), candidates.end());
backtracking(result, candidates, 0, target);
return result;
}
};
*131. 分割回文串
本题较为复杂,没有掌握的很熟练。思路来源
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
n
)
O(n)
O(n)
// c++
class Solution {
public:
vector<string> cur_result;
bool isPalindrome(const string& s, int left, int right){
if(right == left) return true;
for(; left<right; left++, right--){
if(s[left]!=s[right]) return false;
}
return true;
}
void backtracking(vector<vector<string>> & result, const string& s, int left){
if(left >= s.size()){
result.emplace_back(cur_result);
return;
}
for(int i=left; i<s.size(); i++){
if(isPalindrome(s, left, i)){
string cur = s.substr(left, i-left+1);
cur_result.emplace_back(cur);
}else continue;
backtracking(result, s, i+1);
cur_result.pop_back();
}
}
vector<vector<string>> partition(string s) {
vector<vector<string>> result;
backtracking(result, s, 0);
return result;
}
};