LeetCode刷题笔记【21】:回溯专题-4(复原IP地址、子集、子集II)

前置知识

参考前文

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

93.复原IP地址

题目描述

在这里插入图片描述

LeetCode链接:https://leetcode.cn/problems/restore-ip-addresses/description/

解题思路

思路: 两个子函数, 一个是backtrack用于回溯遍历, 一个check用于检查子串是否合法;
backtrack中传入一个int参数表示当前子串是ip地址中的第几个, 如果是第4个的话要直接查看从当前位到最后位是否合法;
check中一方面检查数字是否在0-255之间, 另一方面看是否有先导0;

代码

class Solution {
private:
    vector<string> ans;
    // string path;
    bool check(string& s, int left, int right){
        if(left > right || right-left>3)
            return false;
        if(s[left]=='0' && left!=right)
            return false;
        int sum=0;
        for(int i=1; right>=left; right--, i*=10){
            sum += (s[right]-'0') * i;
        }
        if(sum > 255)
            return false;
        return true;
    }
    void backtrack(string& s, int left, int counter){
        if(counter==4 && check(s, left, s.size()-1)){
            ans.push_back(s);
            return;
        }
        for(int right=left; right<s.size(); ++right){
            if(check(s, left, right)){
                s.insert(s.begin() + right + 1, '.');
                backtrack(s, right+2, counter+1);
                s.erase(s.begin() + right + 1);
            }else{
                break;
            }
        }
        return;
    }
public:
    vector<string> restoreIpAddresses(string s) {
        if (s.size() < 4 || s.size() > 12)
            return ans;
        backtrack(s, 0, 1);
        return ans;
    }
};

78.子集

题目描述

在这里插入图片描述

LeetCode链接:https://leetcode.cn/problems/subsets/description/

《代》经典模板 - 遍历树种所有节点

还是用经典回溯模板, 但是之前碰到的回溯是在"找叶子节点", 但是本题中其实是在"遍历整棵树, 记录所有节点"

如果把 子集问题、组合问题、分割问题都抽象为一棵树的话,那么组合问题和分割问题都是收集树的叶子节点,而子集问题是找树的所有节点!

class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& nums, int startIndex) {
        result.push_back(path); // 收集子集,要放在终止添加的上面,否则会漏掉自己
        if (startIndex >= nums.size()) { // 终止条件可以不加
            return;
        }
        for (int i = startIndex; i < nums.size(); i++) {
            path.push_back(nums[i]);
            backtracking(nums, i + 1);
            path.pop_back();
        }
    }
public:
    vector<vector<int>> subsets(vector<int>& nums) {
        result.clear();
        path.clear();
        backtracking(nums, 0);
        return result;
    }
};

在这里插入图片描述

LeetCode题解思路 - 遍历沿途选择是否加入(寻找叶节点)

思路: 还是回溯, 但形式变为: 对于nums中的每个数, 可以选择加入path或者不加入path, 然后遍历下一个数.

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

在这里插入图片描述
观察这张图和上一张图, 思考二者异同
后者, 最后还是在求叶子节点, 不过过程中的分支是"是否加入当前节点"
前者, 在遍历并记录整棵树的所有节点, 分支变为"选择当前节点之后的哪个节点进行遍历"

90.子集II

题目描述

在这里插入图片描述

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

解题思路

思路: 参考<78. 子集>
但是需要添加去重操作, 其实也不难, 也就是当nums[i]==nums[i+1]的时候continue罢了

《代》经典模板

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

LeetCode思路

此时需要在"没有选择上一个元素, 并且当前元素和上一个元素相同"时, 跳过当前元素

class Solution {
private:
    vector<vector<int>> ans;
    vector<int> path;
    void backtrack(vector<int>& nums, int index, bool selected){// 所以这里加了一个bool类型变量记录上一个节点是否被选择
        if(index==nums.size()){
            ans.push_back(path);
            return;
        }
        backtrack(nums, index+1, false);
        if(!selected && index>0 && nums[index]==nums[index-1])
            return;
        path.push_back(nums[index]);
        backtrack(nums, index+1, true);
        path.pop_back();
        return;
    }
public:
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        backtrack(nums, 0, false);
        return ans;
    }
};

总结

今天的题可以看出: 将回溯过程抽象为树, 对于我们理解回溯是非常有帮助的, 很复杂的过程, 转化为"求叶子节点/遍历记录所有节点", 就变得十分清晰.

于此同时, 针对回溯问题, 我们已经学习了组合, 子集, 排列, 分割. 即将学完,

这几类问题解法相似, 但是各有各的特点和细节需要注意, 过程中要注意对比总结, 在最后学完了回溯的所有题型后, 做个总结.

在这里插入图片描述

本文参考:
复原IP地址
子集
子集II

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值