39m. 组合总和
方法一:回溯
用时:9m35s
思路
- 时间复杂度: O ( n ⋅ 2 n ) O(n\cdot 2^n) O(n⋅2n)。KaTeX parse error: Expected 'EOF', got '&' at position 2: 2&̲n是组合的个数的上限,将结果复制到结果数组中的时间复杂度为 O ( n ) O(n) O(n),所以总的时间复杂度的上界是 O ( n ⋅ 2 n ) O(n\cdot 2^n) O(n⋅2n)。
- 空间复杂度: O ( n ) O(n) O(n),取决于递归的深度。
C++代码
class Solution {
private:
vector<vector<int>> res;
vector<int> path;
int sum;
void backTracking(vector<int>& candidates, int target, int begin) {
if (sum == target) res.push_back(path); // 当前和等于目标值则记录当前的path
else if (sum < target) { // 当前和小于目标值则继续递归,大于则终止递归
for (int i = begin; i < candidates.size(); ++i) {
sum += candidates[i];
path.push_back(candidates[i]);
backTracking(candidates, target, i); // 递归
sum -= candidates[i]; // 回溯
path.pop_back();
}
}
}
public:
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
sum = 0;
backTracking(candidates, target, 0);
return res;
}
};
看完讲解的思考
无。
代码实现遇到的问题
无。
40m. 组合总和II
方法一:
用时:18m48s
思路
本题由于candidates中会有重复的元素,但是结果中又不能有重复的组合,所以需要去重。可以先将candidates先排序,然后在遍历的时候跳过重复的元素。
- 时间复杂度: O ( n ⋅ 2 n ) O(n\cdot 2^n) O(n⋅2n)。
- 空间复杂度: O ( n ) O(n) O(n)。
C++代码
class Solution {
private:
vector<vector<int>> res;
vector<int> path;
int sum;
void backTracking(vector<int>& candidates, int target, int begin) {
if (sum == target) res.push_back(path); // 当前和等于目标值,则记录当前组合并终止递归
else if (sum < target) { // 若当前和小于目标值则继续递归,若大于目标值则终止递归
for (int i = begin; i < candidates.size(); ++i) {
if (i > begin && candidates[i] == candidates[i - 1]) continue; // 去重,当前元素若与上一个元素相同则跳过
if (candidates[i] <= target) {
sum += candidates[i];
path.push_back(candidates[i]);
backTracking(candidates, target, i + 1); // 递归
sum -= candidates[i]; // 回溯
path.pop_back();
} else break; // 若当前元素已经大于目标值,则终止遍历,因为数组已经排序过了,后续元素也是大于目标值的
}
}
}
public:
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
sum = 0;
sort(candidates.begin(), candidates.end()); // 先排序,用于去重
backTracking(candidates, target, 0);
return res;
}
};
看完讲解的思考
无。
代码实现遇到的问题
无。
131m. 分割回文串
方法一:回溯+双指针
用时:16m14s
思路
回溯搜索,然后用双指针判断当前子串是否是回文串。
- 时间复杂度: O ( n 2 ⋅ 2 n ) O(n^2 \cdot 2^n) O(n2⋅2n)。每一层递归中,双指针判断子串是否是回文串的时间复杂度为 O ( n ) O(n) O(n),将子串拷贝到结果数组中的时间复杂度为 O ( n ) O(n) O(n)。
- 空间复杂度: O ( n ) O(n) O(n)。
C++代码
class Solution {
private:
vector<vector<string>> res;
vector<string> path;
void backTracking(string& s, int begin) {
if (begin == s.length()) res.push_back(path); // 如果字符串已全部遍历,则将结果记录并终止递归
else {
for (int i = 1; i <= s.length() - begin; ++i) {
if (isPattern(s, begin, begin + i - 1)) { // 如果字符串是回文串,则继续递归,不是回文串则跳过
path.push_back(string(s, begin, i));
backTracking(s, begin + i); // 递归
path.pop_back(); // 回溯
}
}
}
}
bool isPalindrome(string& s, int begin, int end) {
// 判断字符串是不是回文串
while (begin < end) if (s[begin++] != s[end--]) return false;
return true;
}
public:
vector<vector<string>> partition(string s) {
backTracking(s, 0);
return res;
}
};
方法二:回溯+动态规划
用时:11m6s
思路
用动态规划记录每个子串是否是回文串,这样在回溯搜索中直接查询即可。
动态规划判断每个子串是否是回文串:
对于一个字符串,当串头和串尾字符相等并且除去头和尾的子串是回文串时,该字符串就是回文串,所以如果我们知道了中间子串是回文串的话,就不用再从头到尾遍历一遍字符串了。
用一个二维数组isPalindrome记录每个子串是否是回文串,其中isPalindrome[i][j]表示s中以第i个字符开始、第j个字符结束的子串是否是回文串。
遍历的时候,i从后往前遍历,这样就能保证在判断第i个字符开始的子串时,以第i+1个字符开始的子串已经判断过了,这样就能使用上述所说的性质以
O
(
1
)
O(1)
O(1)的时间复杂度判断当前子串是否是回文串。
- 时间复杂度: O ( n ⋅ 2 n ) O(n \cdot 2^n) O(n⋅2n)。将子串拷贝到结果数组中的时间复杂度为 O ( n ) O(n) O(n)。
- 空间复杂度: O ( n 2 ) O(n^2) O(n2)。记录每个子串是否是回文串的数组的空间复杂度为 O ( n 2 ) O(n^2) O(n2)。
C++代码
class Solution {
private:
vector<vector<string>> res;
vector<string> path;
vector<vector<bool>> isPalindrome;
void backTracking(string& s, int begin) {
if (begin == s.length()) res.push_back(path); // 如果字符串已全部遍历,则将结果记录并终止递归
else {
for (int i = 1; i <= s.length() - begin; ++i) {
if (isPalindrome[begin][begin + i - 1]) { // 如果字符串是回文串,则继续递归,不是回文串则跳过
path.push_back(string(s, begin, i));
backTracking(s, begin + i);
path.pop_back();
}
}
}
}
public:
vector<vector<string>> partition(string s) {
// 动态规划
isPalindrome.assign(s.length(), vector<bool>(s.length(), true));
for (int i = isPalindrome.size() - 1; i >= 0; --i) { // i从后往前遍历
for (int j = i + 1; j < isPalindrome.size(); ++j) {
isPalindrome[i][j] = (s[i] == s[j]) && isPalindrome[i + 1][j - 1];
}
}
// 回溯搜索
backTracking(s, 0);
return res;
}
};
看完讲解的思考
无。
代码实现遇到的问题
无。
最后的碎碎念
回溯感觉难度也还好?(希望后面别被打脸。)