【代码随想录训练营第42期 Day24打卡 回溯Part3 - LeetCode 93.复原IP地址 78.子集 90.子集II

目录

一、做题心得

二、题目与题解

题目一:93.复原IP地址

题目链接

题解:回溯--分割问题

题目二:78.子集

题目链接

题解:回溯--子集问题

题目三:90.子集II

题目链接

题解:回溯--子集问题

三、小结


一、做题心得

今天的题个人感觉第一道 93. 复原 IP 地址 - 力扣(LeetCode)还是挺有难度的,虽然跟昨天打卡的分割回文串很相似,但是自己做的时候还是有点吃力。后边两道题就很简单了,感觉和前两天练的组合问题差不多,个人感觉问题不大。

这里直接开始今天的题目吧。

二、题目与题解

题目一:93.复原IP地址

题目链接

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

有效 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:

输入:s = "25525511135"
输出:["255.255.11.135","255.255.111.35"]

示例 2:

输入:s = "0000"
输出:["0.0.0.0"]

示例 3:

输入:s = "101023"
输出:["1.0.10.23","1.0.102.3","10.1.0.23","10.10.2.3","101.0.2.3"]

提示:

  • 1 <= s.length <= 20
  • s 仅由数字组成
题解:回溯--分割问题

这个题也是很明显的分割问题。 、

分析题意:有效IP地址分为4个部分,每一部分由.隔开,每一部分都有限制要求(这个限制要求往往会出现if从句,如果复杂的话需要自定义函数)

关键

1.自定义函数判断IP地址某部分是否有效(即是否符合题目要求)

2.如何截取字符串s的各种子串:substr()函数

3.如何实现剪枝(具体看代码):三处--两处是特殊情况的单独处理,一处是循环遍历对终止条件的缩小

4.终止条件是什么:递归遍历完整个字符串的同时,IP地址恰好被分为4个部分

想清楚上边四点,这道题也就好解决了,剩下的就跟昨天 131. 分割回文串  一个道理。

代码如下:

class Solution {  
public:  
    vector<string> ans;  
    vector<string> vec;
    bool isRange(string substring) {        //判断字符串是否为有效IP地址的一部分
        if (substring.size() != 1 && substring[0] == '0') {        //前导0无效(如012,023等等)
            return false;  
        }  
        int num = stoi(substring);      //将字符串转化为数字(字符串只包含数字,不考虑负数)
        return num <= 255;          
    }   
    void backtrack(string& s, int start) {    
            if (vec.size() == 4 && start == s.size()) {     //终止条件:当vec中存储了4个部分,并且刚好遍历完了整个字符串s时,说明找到了一个有效的IP地址
                string ip = "";  
                for (int i = 0; i < vec.size(); ++i) {      //vec每一部分结束后添加.(最终结果存放在ip里)
                    ip += vec[i];  
                    if (i < vec.size() - 1) {  
                        ip += ".";  
                    }  
                }  
                ans.push_back(ip);    
            return;  
        }  
        if (start == s.size() && vec.size() != 4) {    //剪枝1:当遍历完了整个s但IP地址部分数不为4时,重新返回递归
            return;
        }
        for (int i = start; i < start + 3 && i < s.size(); i++) {   //剪枝2:i < start + 3表示IP地址每一部分不超过三位数
            string substring = s.substr(start, i - start + 1);     //截取字符串start到i的子串
            if (!isRange(substring)) {  
                continue;
            }  
            vec.push_back(substring);  
            backtrack(s, i + 1);      
            vec.pop_back();  
        }  
    }  
    vector<string> restoreIpAddresses(string s) {  
        if (s.size() < 4 || s.size() > 12) {       //剪枝3:不可能存在有效IP地址情况直接返回空向量
            return ans;
        }
        backtrack(s, 0);  
        return ans;  
    }  
};

题目二:78.子集

题目链接

78. 子集 - 力扣(LeetCode)

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的

子集

(幂集)。

解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

示例 1:

输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]

示例 2:

输入:nums = [0]
输出:[[],[0]]

提示:

  • 1 <= nums.length <= 10
  • -10 <= nums[i] <= 10
  • nums 中的所有元素 互不相同
题解:回溯--子集问题

 子集问题个人感觉跟组合问题差不多,可能最大的不同就在于终止条件那里。

这里我们需要注意了,对于对于子集问题:当我们要得到一个数组集合的全部子集,其实是没有终止条件的(当然,你也可以想成有,就是全部都自然遍历添加完,不过这其实跟没有也没区别),也就是说,我们只需要把每一个递归得到的数组添加到结果里边去即可。这里就要求我们对模板的有效运用了,不要没有终止条件就硬想半天。

代码如下:

class Solution {
public:
    vector<vector<int>> ans;
    vector<int> vec;
    void backtrack(vector<int>& nums, int start) {
        ans.push_back(vec);        //注意:所有子集都要添加,没有终止条件限制      
        for (int i = start; i < nums.size(); i++) {
            vec.push_back(nums[i]);
            backtrack(nums, i + 1);
            vec.pop_back();
        }    
    }
    vector<vector<int>> subsets(vector<int>& nums) {
        backtrack(nums, 0);
        return ans;
    }
};

题目三:90.子集II

题目链接

给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的 

子集

(幂集)。

解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。

示例 1:

输入:nums = [1,2,2]
输出:[[],[1],[1,2],[1,2,2],[2],[2,2]]

示例 2:

输入:nums = [0]
输出:[[],[0]]

提示:

  • 1 <= nums.length <= 10
  • -10 <= nums[i] <= 10
题解:回溯--子集问题

这个题算是上面那道的进化版吧,不过思路也差不多,只是数组里出现了重复元素,而要求结果中不能出现重复子集--这不就和昨天打卡不能出现重复组合一样了:对数组排序 + 跳过数组相邻相同的元素(横向同层处理使结果不出现重复子集)。

不理解的话,可以看看昨天的打卡内容,这里就不做分析了。

代码如下:

class Solution {
public:
    vector<vector<int>> ans;
    vector<int> vec;
    void backtrack(vector<int>& nums, int start) {
        ans.push_back(vec);
        for (int i = start; i < nums.size(); i++) {
            if (i > start && nums[i] == nums[i - 1]) {      //横向去重:用以跳过同一树层使用过的(重复)元素
                continue;       //注意这里是continue而不是break(break会直接跳出循环,这样一旦出现重复元素,会完全停止遍历剩余的元素,这会导致生成的子集不完整)
            }
            vec.push_back(nums[i]);
            backtrack(nums, i + 1);
            vec.pop_back();
        }
    }
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        sort(nums.begin(), nums.end());         //先排序,这样重复的元素就会相邻
        backtrack(nums, 0);
        return ans;
    }
};

三、小结

今天的打卡就到此结束了,后边也会继续加油。最后,我是算法小白,但也希望终有所获。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值