今天巩固了回溯算法:组合问题,开始接触 回溯算法:切割问题。
第一题:
简介:
本题的难点在于数组中的每个数都可以重复使用,所以当我们进行回溯时需要考虑好每次给递归for循环每次从什么位置开始。然后回溯过后为了不和前面的组合重复,startindex该如何变化。
代码实现:
class Solution {
public:
int sum(vector<int> num){
int summ=0;
for(int i:num){
summ +=i;
}
return summ;
}
void backtracking(vector<int>& candidates,vector<int>& path,vector<vector<int>>& result,int target,int startindex){
int a = sum(path);
if(a == target){
result.push_back(path);
return;
}
if(a >target){
return;
}
for(int i =startindex;i<candidates.size();i++){
path.push_back(candidates[i]);
backtracking(candidates,path,result,target,i);
path.pop_back();
}
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
vector<vector<int>> result;
vector<int> path;
backtracking(candidates,path,result,target,0);
return result;
}
};
第二题:
简介:
本题思路与上文相似,但是要注意一点不同的是,如何保证数组不会出现下列图片像【1,2,5】【2,1,5】一样的重复数组。
然后如何避免当数组中有数值相同的元素是如何避免出现相同的值。如何剪枝去重。
代码实现:
class Solution {
public:
int sum(vector<int> num){
int summ=0;
for(int i:num){
summ +=i;
}
return summ;
}
void backtracking(vector<int>& handle,vector<int>& candidates,vector<int>& path,vector<vector<int>>& result,int target,int startindex){
int a = sum(path);
if(a == target){
result.push_back(path);
return;
}
if(a >target){
return;
}
for(int i =startindex;i<candidates.size();i++){
if(i>0&&candidates[i]==candidates[i-1]&&handle[i-1]==0){
continue;
}
path.push_back(candidates[i]);
handle[i]=1;
backtracking(handle,candidates,path,result,target,i+1);
path.pop_back();
handle[i]=0;
}
}
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
vector<vector<int>> result;
vector<int> path;
vector<int> handle(candidates.size(),0);
sort(candidates.begin(),candidates.end());
backtracking(handle,candidates,path,result,target,0);
return result;
}
};
第三题:
简介:
本题的思路我们如何进行切割字符串(通过将startindex当做一个隔板,然后通过不断递归插入隔板进行切割。)
如何判断是否是回文字符串(双指针法前后各一个指针,不断向中间靠近,如果两指针所指项都相同,就可以判断是回文子串)
代码实现:
(未优化)
class Solution {
public:
vector<vector<string>> result;
vector<string> path; // 放已经回文的子串
void backtracking (const string& s, int startIndex) {
// 如果起始位置已经大于s的大小,说明已经找到了一组分割方案了
if (startIndex >= s.size()) {
result.push_back(path);
return;
}
for (int i = startIndex; i < s.size(); i++) {
if (isPalindrome(s, startIndex, i)) { // 是回文子串
// 获取[startIndex,i]在s中的子串
string str = s.substr(startIndex, i - startIndex + 1);
path.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;
}
public:
vector<vector<string>> partition(string s) {
result.clear();
path.clear();
backtracking(s, 0);
return result;
}
};
(通过动态规划进行优化)
class Solution {
private:
vector<vector<string>> result;
vector<string> path; // 放已经回文的子串
vector<vector<bool>> isPalindrome; // 放事先计算好的是否回文子串的结果
void backtracking (const string& s, int startIndex) {
// 如果起始位置已经大于s的大小,说明已经找到了一组分割方案了
if (startIndex >= s.size()) {
result.push_back(path);
return;
}
for (int i = startIndex; i < s.size(); i++) {
if (isPalindrome[startIndex][i]) { // 是回文子串
// 获取[startIndex,i]在s中的子串
string str = s.substr(startIndex, i - startIndex + 1);
path.push_back(str);
} else { // 不是回文,跳过
continue;
}
backtracking(s, i + 1); // 寻找i+1为起始位置的子串
path.pop_back(); // 回溯过程,弹出本次已经添加的子串
}
}
void computePalindrome(const string& s) {
// isPalindrome[i][j] 代表 s[i:j](双边包括)是否是回文字串
isPalindrome.resize(s.size(), vector<bool>(s.size(), false)); // 根据字符串s, 刷新布尔矩阵的大小
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]);}
}
}
}
public:
vector<vector<string>> partition(string s) {
result.clear();
path.clear();
computePalindrome(s);
backtracking(s, 0);
return result;
}
};
总结:
今天,第一次接触 回溯问题:切割问题 感觉有点复杂,需要多加理解。继续加油!