算法整理——【回溯法进阶(1)】

上篇博客算法整理——【回溯法简述】-CSDN博客简单介绍了回溯算法,并以一道基础例题作为示范展示回溯法。本篇将继续整理和分析更多回溯法解决的问题。

一、回溯算法中组合的去重

例题为40. 组合总和 II - 力扣(LeetCode),给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的每个数字在每个组合中只能使用一次

本题给的候选集合里有重复的数,但是组合出的结果又不能重复,所以我们要去重。

一种去重方法是按照之前的回溯组合方法,得到所有的组合之后,用map或者别的对组合进行去重。这种做法会容易超时。

另一种就是在搜索的过程中进行去重。并且是在层上去重(树层去重),不是树枝去重。

首先需要两个全局变量,一维数组path和二位数组result(存放结果)。

按照回溯三部曲,首先要确定回溯函数的参数和返回值。参数需要候选集、目标和、目前的和、startindex、是否包含该元素的数组。函数为void backtracking(nums, targetsum, sum, startindex, used);

然后确认终止条件,sum>targetsum则return;sum==targetsum就把path存入result。

第三步是单层逻辑,重点是去重的逻辑,如果该元素和之前的元素相同并且前一个元素的used为0(回溯过的),则return(去除)。(前一个used为1为树枝重复,不用去除)

整体代码如下:

class Solution {
public:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& candidates, int target, int sum, int start, vector<bool>& used)
    {
        if(sum == target)
        {
            result.push_back(path);
            return;
        }
        for(int i = start; i<candidates.size() && sum + candidates[i] <= target; i++)
        {
            if(i>0&&candidates[i] == candidates[i-1] && used[i-1] == false)
            {
                continue;
            }
            sum+=candidates[i];
            path.push_back(candidates[i]);
            used[i]=1;
            backtracking(candidates,target,sum,i+1,used);
            used[i] = 0;
            sum-=candidates[i];
            path.pop_back();
        }
    }

    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        vector<bool> used(candidates.size(), false);
        // 首先把给candidates排序,让其相同的元素都挨在一起。
        sort(candidates.begin(), candidates.end());
        backtracking(candidates, target, 0, 0, used);
        return result;
    }
};

二、回溯算法分割回文串

例题为131. 分割回文串 - 力扣(LeetCode),给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串。返回 s 所有可能的分割方案。

切割问题可以理解为组合问题,如对abcdef进行切割,对于第一层,可以选择切割a,可以切割ab,可以切割abc等等等。

接着进行回溯三部曲,第一步确认函数参数和返回值。还是跟其他的回溯题目一样,我们需要一维数组path存切割后的子串,需要二维数组result存放结果。本题递归函数参数还需要startIndex,因为切割过的地方,不能重复切割,和组合问题也是保持一致的。

然后确定终止条件,如果起始位置大于s的大小则说明已经找到一组分割方案了,把path存入result。

最后确定单层逻辑,单层逻辑为判断这个子串是否为回文,不是则跳过,是则push进path。然后继续回溯。

整体代码如下:

class Solution {
public:
    vector<vector<string>> result;
    vector<string> path;
    void backtracking(string s, int start)
    {
        if(start>=s.size())
        {
            result.push_back(path);
            return;
        }
        for(int i = start; i<s.size(); i++)//切割[start,i]
        {
            if(isP(s,start,i))//[start,i]是否回文
            {
                string str = s.substr(start,i-start+1);//s.substr(pos, len)
                path.push_back(str);
            }
            else
            {
                continue;
            }
            backtracking(s,i+1);
            path.pop_back();
        }
    }
    bool isP(const string s, int start,int end)
    {
        for(int p = start,q = end;p<q; p++,q--)
        {
            if(s[p]!=s[q])
            {
                return 0;
            }
        }
        return 1;
    }
    vector<vector<string>> partition(string s) {
        backtracking(s,0);
        return result;
    }
};

说明:本文为作者整理知识点用于复习巩固,参考了代码随想录的讲解,有问题可以联系作者欢迎讨论~

  • 28
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值