代码随想录刷题攻略---回溯2---分割&子集&排列

例题1:分割回文串 

给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串。返回 s 所有可能的分割方案。

这道题涉及到字符串的切割,其实跟组合问题有点像:

  • 组合问题:选取一个a之后,在bcdef中再去选取第二个,选取b之后在cdef中再选取第三个.....。
  • 切割问题:切割一个a之后,在bcdef中再去切割第二段,切割b之后在cdef中再切割第三段.....。

在树层的遍历中,我们每次都选下1个元素作为切割对象;

在树枝的遍历中,我们根据树层选择的元素为第1个切割点,切割剩下的数据。

  • 递归函数参数

全局变量数组path存放切割后回文的子串,二维数组result存放结果集。 (这两个参数可以放到函数参数里)

本题递归函数参数还需要startIndex,因为切割过的地方,不能重复切割,和组合问题也是保持一致的。

void backtracing(string& s, int startindex)
  • 终止条件

当 startindex 到了字符串 s 的最后,终止遍历,将当前保存的 tmp 字符串数组加入 res。(我们在单层循环逻辑中判断当前子串是否是回文,保证正确性)

  • 单层搜索的逻辑

 在每一层循环中,我们先寻找一个开始切割的点,根据这个切割点进行递归。具体来说,寻找了一个切割点后,我们判断在这个切割点的右边先寻找一个回文串,把该回文串存入我们的一维数组,然后将该回文串的右边作为新的切割点,递归切割剩下的子串。

class Solution {
public:
    vector<vector<string>> res;
    vector<string> tmp;
    bool ishuiwen(string &substring){//判断回文串
        string s = substring;
        std::reverse(substring.begin(),substring.end());
        if(s == substring)
            return true;
        return  false;
    }
    void backtracing(string& s, int startindex){
        if(startindex == s.size()){
            res.push_back(tmp);
            return;
        }
        for(int i = startindex; i < s.size(); i++){
            string str = s.substr(startindex, i - startindex + 1);
            if(ishuiwen(str)){//左闭右闭区间
                tmp.push_back(str);//选取符合回文的子串
            }
            else
                continue;
            //选好了,继续切割下一个
            backtracing(s, i+1);
            tmp.pop_back();
        }
    }

    vector<vector<string>> partition(string s) {
        backtracing(s,0);
        return res;
    }
};

例题2:复原IP地址

有效 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 '.' 分隔。

  • 例如:"0.1.2.201" 和 "192.168.1.1" 是 有效 IP 地址,但是 "0.011.255.245""192.168.1.312" 和 "192.168@1.1" 是 无效 IP 地址。

给定一个只包含数字的字符串 s ,用以表示一个 IP 地址,返回所有可能的有效 IP 地址,这些地址可以通过在 s 中插入 '.' 来形成。你 不能 重新排序或删除 s 中的任何数字。你可以按 任何 顺序返回答案。

 这道题可以说是例题1的加强版,因为还涉及到逗点的增加

所以在调用函数的参数中,还会使用一个 numpoint 来表示已经被我们使用了多少个逗点了。

终止条件就是字符串已经被我们用 3 个逗点分成了 4 段。

单层循环逻辑:

跟例题1类似,先判断当前子串是否合法,合法的话我们加入1个逗点,继续判断下一个子串。我一开始想用字符串类型来存储、添加、删除子串,但是删除的操作成本比较高,于是我们直接在原字符串上添加、删除逗点。

class Solution {
public:
    vector<string> res;
    bool iseffective(const string& s){
        if(s.size()==0) return false;
        if(s[0] == '0' && s.size() > 1)
            return false;
        int num = 0;
        for (int i = 0; i < s.size(); i++) {
            if (s[i] > '9' || s[i] < '0') { // 遇到非数字字符不合法
                return false;
            }
            num = num * 10 + (s[i] - '0');
            if (num > 255) { // 如果大于255了不合法
                return false;
            }
        }
        return true;
    }

    void backtracing(string& s, int startindex, int num){
        if(num == 0)//逗点加完了
        {//这里还要检查第4段字符串是否合法
            string str = s.substr(startindex, s.size()-1);
            if(iseffective(str))
                res.push_back(s);
            return ;
        }
        for(int i = startindex; i < s.size(); i++){
            string str = s.substr(startindex, i - startindex + 1);
            if(iseffective(str)){
                s.insert(s.begin()+i+1,'.');//insert为sting可用,const string不行
            }
            else 
                continue;
            backtracing(s, i+2, num-1);//跳过新加的逗点
            s.erase(s.begin()+i+1);
        }
    }

    vector<string> restoreIpAddresses(string s) {
        backtracing(s,0,3);
        return res;
    }
};

例题3:子集II

给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

说明:解集不能包含重复的子集。

  •  这道题跟回溯中的组合法的题很像,都是“树层不可有重复数据,树枝可以有重复数据”,所以依然用一个 used 数组来实现树层的去重。所以调用函数的参数就不用说了,
void backtracing(vector<int>& nums, int startindex, vector<bool>& used)
  1. 但是终止条件有点不一样,这道题中,每个树枝的值都会作为结果的子集输出,所以没有终止条件,每次调用时首先把当层数据存入结果里
// if(startindex == nums.size()-1)
        // {
        //     res.push_back(tmp);
        //     return ;
        // }终止条件这样写不对
        res.push_back(tmp);//树枝的每一层都可以放入数组中存储

单层循环逻辑:

  1. 我们每访问树层的数据时,把该数据的 used 位标为 true,然后开始递归(形成树枝),树枝上再遇到相同的数字,是可以放入当前结果集的。
  2. 如果我们访问树层数据时,发现两个数据一样。并且前一个数字的 used 位为 false,说明该数字的树枝已经形成了,我们再继续对这个数进行递归,就会产生重复的子集了。
class Solution {
public:
    vector<vector<int>> res;
    vector<int> tmp;
    void backtracing(vector<int>& nums, int startindex, vector<bool>& used){
        // if(startindex == nums.size()-1)
        // {
        //     res.push_back(tmp);
        //     return ;
        // }终止条件这样写不对
        res.push_back(tmp);//树枝的每一层都可以放入数组中存储
        for(int i = startindex; i < nums.size(); i++){
            if(i > 0 && nums[i] == nums[i-1] && used[i-1] == false){//两个元素一样但被使用了
                continue;
            }
            used[i] = true;
            tmp.push_back(nums[i]);
            backtracing(nums, i+1, used);
            tmp.pop_back();
            used[i] = false;
        }
    }

    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        vector<bool> used(nums.size(),false);
        //必须要排序
        sort(nums.begin(),nums.end());
        backtracing(nums,0,used);
        return res;    
    }
};

例题4:全排列

 给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。

 

 全排列就是找出给定数组的所有元素不同的组合,跟组合不同的地方在于它每次都会查看并运用,所以我们要用 used 数组记录剩下还没用的数据有哪些。

  • 递归函数参数

首先排列是有序的,也就是说 [1,2] 和 [2,1] 是两个集合,这和之前分析的子集以及组合所不同的地方

可以看出元素1在[1,2]中已经使用过了,但是在[2,1]中还要在使用一次1,所以处理排列问题就不用使用startIndex了。

vector<vector<int>> result;
vector<int> path;
void backtracking (vector<int>& nums, vector<bool>& used)
  •  递归终止条件

 当收集元素数组 path 的大小达到与 nums 数组一样大时,说明找完了一个全排列。

  • 单层搜索的逻辑

因为排列问题,每次都要从头开始搜索,例如元素1在[1,2]中已经使用过了,但是在[2,1]中还要再使用一次1。

而used数组,其实就是记录此时path里都有哪些元素使用了,一个排列里一个元素只能使用一次

整体代码如下: 

class Solution {
public:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking (vector<int>& nums, vector<bool>& used) {
        // 此时说明找到了一组
        if (path.size() == nums.size()) {
            result.push_back(path);
            return;
        }
        for (int i = 0; i < nums.size(); i++) {//每次都从头遍历
            if (used[i] == true) continue; // path里已经收录的元素,直接跳过
            used[i] = true;
            path.push_back(nums[i]);
            backtracking(nums, used);
            path.pop_back();
            used[i] = false;
        }
    }
    vector<vector<int>> permute(vector<int>& nums) {
        vector<bool> used(nums.size(), false);
        backtracking(nums, used);
        return result;
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值