代码随想录-训练营-day21

今天我们继续我们的分割问题。

93. 复原 IP 地址 - 力扣(LeetCode)

这个题将会给我们一个数字形成的字符串,要求我们将其转换为一个合理的IP地址,返回所有的合理的IP地址。

毋庸置疑这是一个经典的分割问题,我们需要检查挨个分割这个数字形成的字符串后后续的情况,让我们开始回溯:

第一步,确定终止条件。可能有人会像我一样一开始依然将startIndex>=s.size()作为终止条件,但是实际上这个思路不够完整:即使我一个点没有打也会触发终止,所以真正关键的部分在于我们已经打的点的数量:当我们已经向字符串中加入三个点后我们就对第三个点后的子串进行判断,如果他满足我们的IP要求我们就将这个string加入最后返回的容器中即可。

第二步,我们要确定遍历要执行的流程,首先我们要先写好一个判断子串是否满足IP地址要求的函数,具体的IP地址的要求包括不能含有前导0和在0到255之间。然后我们先维护一个startIndex,然后判断startIndex到i之间的子串是否满足IP要求,满足的话我们就在i的后方插入一点,并从该点后一位开始递归。我们还需要维护一个点的数量,每插入一个点就加一。

第三步,进行回溯,我们将这个点去掉即可。

代码如下:

class Solution {
public:
    vector<string> res;

    void backtrack(string s, int startIndex, int pointNum) {
        if (pointNum == 3) {
            if (helper(s, startIndex, s.size() - 1)) {
                res.push_back(s);
            }
            return;
        }
        for(int i = startIndex; i < s.size(); ++i){
            if(helper(s, startIndex, i)){
                s.insert(s.begin() + i + 1, '.');
                pointNum++;
                backtrack(s, i + 2, pointNum);
                s.erase(s.begin() + i + 1); // 撤销操作
                pointNum--; // 撤销选择
            } else break;
        }
    }

    bool helper(const string& str, int start, int end) {
        if (end - start + 1 > 3 || start > end) return false;
        string seg = str.substr(start, end - start + 1);
        if (seg.size() > 1 && seg[0] == '0') return false;
        int num = stoi(seg);
        if (num >= 0 && num <= 255) return true;
        return false;
    }

    vector<string> restoreIpAddresses(string s) {
        if(s.size() > 12 || s.size() < 4) return {}; // 快速排除不可能的情况
        backtrack(s, 0, 0);
        return res;
    }
};

78. 子集 - 力扣(LeetCode)

完成组合问题与分割问题后,我们来到了子集问题。所谓的子集问题往往是给定一个集合后求满足某种需求的子集集合,与组合问题相比子集是真正的需要遍历所有元素,不存在所谓的剪枝操作。

回到这个题,我们要求这个数组中所有可能的子集,不能包含重复的子集。

首先,终止条件显然就是我们的序号大于数组的大小,这代表着我们已经遍历完了所有元素。不过需要注意的是我们大容器获取小容器的代码执行的位置:如果我们只有在终止时执行的话,我们只能得到叶子节点。

所以我们执行更新大容器的位置应该是在每一次调用backtrack函数的开始。

其余部分和普通的组合问题别无二致,代码如下:

class Solution {
public:
    vector<vector<int>> res;
    vector<int> temp;
    void backtrack(vector<int>& nums,int index){
        res.push_back(temp);
        if(index>=nums.size())return;
        for(int i=index;i<nums.size();++i){
            temp.push_back(nums[i]);
            backtrack(nums, i+1);
            temp.pop_back();
        }
    }
    vector<vector<int>> subsets(vector<int>& nums) {
        backtrack(nums, 0);
        return res;
    }
};

90. 子集 II - 力扣(LeetCode)

这个题在子集题的基础上加入了去重的要求,那么如果还记得之前组合题的去重方法的话,这个题应该也不在话下。

我就直接给代码了,因为确实没啥新东西。

class Solution {
public:
    vector<vector<int>> res;
    vector<int> temp;
    void backtrack(vector<int>& nums,int index){
        res.push_back(temp);
        for(int i=index;i<nums.size();++i){
            if(i>index&&nums[i]==nums[i-1])continue;
            temp.push_back(nums[i]);
            backtrack(nums, i+1);
            temp.pop_back();
        }
    }
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        backtrack(nums, 0);
        return res;
    }
};

这样的话我们的组合,分割,子集问题就刷完了,虽然说是三种问题,但其实本质上是一种问题,也就是组合问题:无序问题,我们的{2,1}与{1,2}是一致的,所以我们用Index控制循环的开始位置而不是每次循环都从0开始,因为只要不重复取值就可以。可是在接下来的排列问题中,就需要我们进一步地关注了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值