《录鼎记》——重启之回溯part03

第七章 回溯算法part03

  • 39. 组合总和
  • 40.组合总和II
  • 131.分割回文串

一、组合总和

力扣题目链接 (opens new window)

回溯三部曲

1、回溯函数参数及返回值,用parh放结果,result放结果集,所以不用返回值,而参数则用集合cand和目标值还有总和和开始值

vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& candidates, int target, int sum, int startIndex)

2、递归终止条件

当总和sum大于目标值的时候返回,当等于目标值的时候送到结果集中。

if (sum > target) {
    return;
}
if (sum == target) {
    result.push_back(path);
    return;
}

3、单层搜索逻辑

这里依然是for循环表示广度,回溯函数表示深度,当for循环取一个值的时候是直接走到底,然后再一步步回溯。

for (int i = startIndex; i < candidates.size(); i++) {
    sum += candidates[i];
    path.push_back(candidates[i]);
    backtracking(candidates, target, sum, i); // 关键点:不用i+1了,表示可以重复读取当前的数
    sum -= candidates[i];   // 回溯
    path.pop_back();        // 回溯
}

整体代码:

class Solution {
public:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& cand,int target,int sum,int startindex){
        if(sum>target){
            return;
        }
        if(sum==target){
            result.push_back(path);
            return;
        }

        for(int i=startindex;i<cand.size();i++){
            sum+=cand[i];
            path.push_back(cand[i]);
            backtracking(cand,target,sum,i);
            sum-=cand[i];
            path.pop_back();
        }
    }
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        result.clear();
        path.clear();
        backtracking(candidates,target,0,0);
        return result;

        
    }
};

下面还给出了剪枝的方法,就是sum加上这个点的值如果大于目标值,就不用进行循环了,但这里注意需要对集合排序,以免错过后续的结果。

for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {

二、组合总和II

力扣题目链接 (opens new window)

class Solution {
public:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& candidates,int target,int sum,int startindex){
        if(sum>target){
            return;
        }
        if(sum==target){
            result.push_back(path);
            return;
        }
        for(int i=startindex;i<candidates.size();i++){
            if(i!=0&&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) {
        result.clear();
        path.clear();
        sort(candidates.begin(),candidates.end());
        backtracking(candidates,target,0,0);
        return result;
    }
};

我自己给写了一段错误代码,这代码的错误在于剪枝减太多了,将组合中有相同元素的情况,比如【1,1,6】的情况减去了。

首先回溯三部曲的前两步其实是差不多的

但是涉及到剪枝,所以在递归函数的定义上,需要去加上一个used集合

这块确实抽象,意思其实就是当取后面的数的时候used[i-1]就是表明回溯的时候是否使用过前面的一个值,如果为true则在path集中是存在前一个数的,反之则不存在。

这里有什么用呢,这个能充分保证,二者共存的集合是可以被使用的,而避免了取前一个或当前数据造成的重复集合。

整体代码:

class Solution {
public:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& candidates,int target,int sum,int startindex,vector<bool> used){
        if(sum>target){
            return;
        }
        if(sum==target){
            result.push_back(path);
            return;
        }
        for(int i=startindex;i<candidates.size();i++){
            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) {
        vector<bool> used(candidates.size(), false);
        result.clear();
        path.clear();
        sort(candidates.begin(),candidates.end());
        backtracking(candidates,target,0,0,used);
        return result;
    }
};

看了下面的startindex剪枝,发现自己上面的方法就差一点,原因是因为回溯中把两个[1,1,6]都减去了。

3、分割回文串

力扣题目链接 (opens new window)

回溯三部曲:

1、递归函数参数

依然是result,path两个数组存放,因为要回溯,所以把string放入,而且还需要startindex。

vector<vector<string>> result;
vector<string> path; // 放已经回文的子串
void backtracking (const string& s, int startIndex) {

2、递归函数的终止条件

如果遍历到最后则返回

void backtracking (const string& s, int startIndex) {
    // 如果起始位置已经大于s的大小,说明已经找到了一组分割方案了
    if (startIndex >= s.size()) {
        result.push_back(path);
        return;
    }
}

3、单层递归逻辑

如果是回文串,则需要切割字符串,将结果放入path同时继续遍历。

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();        // 回溯过程,弹出本次已经填在的子串
}

整体代码:

class Solution {
public:
    vector<vector<string>> result;
    vector<string> path;
    bool ispalind(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 startindex){
        if(startindex>=s.size()){
            result.push_back(path);
            return;
        }
        for(int i=startindex;i<s.size();i++){
            if(ispalind(s,startindex,i)){
                string str =s.substr(startindex,i-startindex+1);
                path.push_back(str);
            }
            else{
                continue;
            }
            backtracking(s,i+1);
            path.pop_back();

        }
    }
    vector<vector<string>> partition(string s) {
        result.clear();
        path.clear();
        backtracking(s,0);
        return result;

    }
};

自己照着三部曲写的代码,感觉还不错

此代码还有优化逻辑:由于证明字符串是否是回文字符串存在重复计算,此时如果直接将一个字符串的所有情况一一取出,在进行查询就可以避免重复计算。

而提前计算的逻辑就是二维布尔数组,i从大到小,j从i开始变大,以保证在i行时,i+行计算完毕,同时在同一行中,每一个结果可以根据前一个结果产生。

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]);}
            }
        }
    }

理解上还是有些难度,但是都读懂了。

拖稿了,抱歉,1个半点结束

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值