LeetCode刷题笔记【20】:回溯专题-3(组合总和、组合总和II、分割回文串)

前置知识

参考前文

参考文章:
LeetCode刷题笔记【18】:回溯专题-1(回溯算法基础知识、用回溯算法解决组合问题)
LeetCode刷题笔记【19】:回溯专题-2(组合总和III、电话号码的字母组合)

39. 组合总和

题目描述

截图

LeetCode链接:https://leetcode.cn/problems/combination-sum/

经典回溯

思路: 使用回溯
注意循环时从index开始, 而不是index+1开始, 因为题目中允许同一个数字反复选取
可以用判断sum是否>target来剪枝(如果candidates里面都为正数的话)

class Solution {
private:
    vector<vector<int>> ans;
    vector<int> path;
    int sum;
public:
    void backtrack(int index, vector<int>& candidates, int target){
        if(sum>target)  return;
        if(sum==target){
            ans.push_back(path);
            return;
        }
        for(int i=index; i<candidates.size(); ++i){
            path.push_back(candidates[i]);
            sum += candidates[i];
            backtrack(i, candidates, target);
            path.pop_back();
            sum -= candidates[i];
        }
        return;
    }
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        ans.clear();
        path.clear();
        sum=0;
        backtrack(0, candidates, target);
        return ans;
    }
};

改进思路

进一步优化:不在递归函数开头判断sum>target, 而在循环体内判断

class Solution {
private:
    vector<vector<int>> ans;
    vector<int> path;
    int sum;
public:
    void backtrack(int index, vector<int>& candidates, int target){
        if(sum==target){
            ans.push_back(path);
            return;
        }
        for(int i=index; i<candidates.size() && sum+candidates[i]<=target; ++i){
            path.push_back(candidates[i]);
            sum += candidates[i];
            backtrack(i, candidates, target);
            path.pop_back();
            sum -= candidates[i];
        }
        return;
    }
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        ans.clear();
        path.clear();
        sum=0;
        sort(candidates.begin(), candidates.end());
        backtrack(0, candidates, target);
        return ans;
    }
};

40.组合总和II

题目描述

在这里插入图片描述

LeetCode链接:https://leetcode.cn/problems/combination-sum-ii/description/

解题思路

在**<39. 组合总和>**的基础上, 还要求过程中candidates中每个数字在每个组合中只能使用一次。
那么就先sort排序, 然后在循环过程中加一个检测: 前后相同则跳过

为什么这样可以达成去重的目的?

我们以本题举例:
输入: candidates = [10,1,2,7,6,1,5], target = 8
如果不加这两行, 那么输出结果是: [[1,1,6],[1,2,5],[1,7],[1,2,5],[1,7],[2,6]]
注意多次输出了[1,2,5],[1,7], 这就是多次使用了第二个1

如果加上重复跳过, 则在到第二个1时, 其会直接跳过, 并不会重复;
那么问题来了, 是否会跳过[1,1,6]呢?
其实不会, 因为是i>index才会跳过, [1,1,6]从第一个1开始, 到第二个1时index=1, 所以i=1的时候虽然 candidates[i]==candidates[i-1], 但是不会跳过.

代码

class Solution {
private:
    vector<vector<int>> ans;
    vector<int> path;
    int sum;
    void backtrack(int index, vector<int>& candidates, int target){
        if(sum>target)  return;
        if(sum==target){
            ans.push_back(path);
            return;
        }
        for(int i=index; i<candidates.size(); ++i){
            if(i>index && candidates[i]==candidates[i-1])//关键, 十分关键
                continue;
            path.push_back(candidates[i]);
            sum += candidates[i];
            backtrack(i+1, candidates, target);
            path.pop_back();
            sum -= candidates[i];
        }
        return;
    }
public:
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        ans.clear();
        path.clear();
        sum=0;
        sort(candidates.begin(), candidates.end());
        backtrack(0, candidates, target);
        return ans;
    }
};

131.分割回文串

题目描述

在这里插入图片描述

LeetCode链接:https://leetcode.cn/problems/palindrome-partitioning/description/

解题思路

思路: 两个辅助函数, 一个bactrck用于实现回溯, 一个check用于检查子串是否是回文串
backtrack中, 传入left, 依次尝试不同的right, 即不同长度的当前子串, 一直尝试到总串结尾

代码

class Solution {
private:
    vector<vector<string>> ans;
    vector<string> path;
    bool check(int left, int right, const string& s){
        while(left<=right){
            if(s[left] != s[right])
                return false;
            left++;
            right--;
        }
        return true;
    }
    void backtrack(int left, const string& s){
        // cout << "left= " << left << endl;
        if(left>=s.size()){
            ans.push_back(path);
            return;
        }
        for(int right=left; right<s.size(); ++right){
            // cout << "right= " << right << endl;
            if(check(left, right, s)){
                // cout << "checking success" << endl;
                path.push_back(s.substr(left, right-left+1));
                backtrack(right+1, s);
                path.pop_back();
            }
        }
    }
public:
    vector<vector<string>> partition(string s) {
        backtrack(0, s);
        return ans;
    }
};

总结

在不同的回溯算法使用方式中, 今天的前两个题很好的比较了"允许重复"和"不允许重复"的情况.
除了昨天总结中提到的:
在这里插入图片描述

LeetCode刷题笔记【19】:回溯专题-2(组合总和III、电话号码的字母组合)

今天还涉及到了需要排序后跳过重复元素的操作, 需要好好对比学习.

而今天的第三题, 是一个很好的展现"如何将一个陌生问题抽象为熟悉的情景, 并使用熟练的工具去解决"的过程;
本身其并不是一个典型的回溯问题, "回文串"的描述更是具有迷惑性, 但我们还是可以通过双指针法, 将其抽象为对字符串中元素的遍历和回溯.

本文参考:
组合总和
组合总和II
分割回文串

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值