代码随想录算法训练营day28 | 93.复原IP地址、78.子集、90.子集II

复原IP地址

链接: 复原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:

输入: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”]

思路

这道题是经典的回溯算法,完全遵循回溯算法模板

做这道题之前我建议先做一下 131. 分割回文串,因为稍微改改 131 题就可以解决这道题了。

131 题的要求是:让你把字符串 s 切分成若干个合法的回文串,返回所有的切分方法。

本题的要求是:让你把字符串 s 切分成 4 个合法的 IP 数字,返回所有的切分方法。

所以我们只要把 131 题的解法稍微改改,重写一个 isValid 函数判断合法的 IP 数字,并保证整棵回溯树只有 4 层(即 track 中只有 4 个子串)即可。

解题方法

具体看代码吧,基本逻辑和 131 题一模一样

复杂度

  • 时间复杂度:O(3^n)
  • 空间复杂度:O(n)

Code

class Solution {
public:
    vector<string> res;
    deque<string> track; // 用双端队列实现列表
    vector<string> restoreIpAddresses(string s) {
        backtrack(s, 0);
        return res;
    }
    // 回溯算法框架
    void backtrack(string s, int start) {
        if (start == s.size() && track.size() == 4) {
            // base case,走到叶子节点
            // 即整个 s 被成功分割为合法的四部分,记下答案
            res.push_back(join_track(track));
            return;
        }
        for (int i = start; i < s.size(); i++) {
            if (!is_valid(s, start, i)) {
                // s[start..i] 不是合法的 ip 数字,不能分割
                continue;
            }
            if (track.size() >= 4) {
                // 已经分解成 4 部分了,不能再分解了
                break;
            }
            string str = s.substr(start, i - start + 1);
            track.push_back(str); // 做选择,把 s[start..i] 放入路径列表中
            // 进入回溯树的下一层,继续切分 s[i+1..]
            backtrack(s, i + 1);
            track.pop_back(); // 撤销选择
        }
    }
    // 判断 s[start..end] 是否是一个合法的 ip 段
    bool is_valid(string s, int start, int end) {
        int len = end - start + 1;
        if (len > 3 || len == 0) { // 长度超出了合理范围
            return false;
        }
        if (len > 1 && s[start] == '0') { // 0x、0xx 不是合法情况
            return false;
        }
        if (stoi(s.substr(start, len)) > 255) { // 判断是否越界
            return false;
        }
        return true;
    }
    // 把路径(双端队列)转成字符串
    string join_track(deque<string> track) {
        string res = "";
        for (int i = 0; i < track.size(); i++) {
            res += track[i];
            if (i < track.size() - 1) {
                res += ".";
            }
        }
        return res;
    }
};

子集

链接: 子集

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

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

示例 1:

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

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

思路

有两种方法解决这道题,这里主要说回溯算法思路,因为比较通用,可以套回溯算法模板。

解题方法

看代码

复杂度

  • 时间复杂度: O(n * 2^n)
  • 空间复杂度:O(n)

Code

class Solution {
    public:
    vector<vector<int>> res;
    vector<vector<int>> subsets(vector<int>& nums) {
        // 记录走过的路径
        vector<int> track;
        backtrack(nums, 0, track);
        return res;
    }

    void backtrack(vector<int>& nums, int start, vector<int>& track) {
        res.push_back(track);
        for (int i = start; i < nums.size(); i++) {
            // 做选择
            track.push_back(nums[i]);
            // 回溯
            backtrack(nums, i + 1, track);
            // 撤销选择
            track.pop_back();
        }
    }
};

子集II

链接: 子集II

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

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

示例 1:

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

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

思路

还是使用回溯算法,但本题的元素可以重复
体现在代码上,需要先进行排序,让相同的元素靠在一起,如果发现 nums[i] == nums[i-1],则跳过

解题方法

看代码

复杂度

  • 时间复杂度:O(n * 2^n)
  • 空间复杂度:O(n)

Code

class Solution {
    vector<vector<int>> res; // 输出结果
    vector<int> track; // 搜索路径
public:
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        sort(nums.begin(), nums.end()); // 排序,让相同的元素靠在一起
        backtrack(nums, 0);
        return res; // 返回结果
    }

    void backtrack(vector<int>& nums, int start) { // start 为当前的枚举位置
        res.emplace_back(track); // 前序位置,每个节点的值都是一个子集
        for(int i = start; i < nums.size(); i++) {
            if (i > start && nums[i] == nums[i - 1]) { // 剪枝逻辑,值相同的相邻树枝,只遍历第一条
                continue;
            }
            track.emplace_back(nums[i]); // 添加至路径
            backtrack(nums, i + 1); // 进入下一层决策树
            track.pop_back(); // 回溯
        }
    }
};

总结

  • 第一题本来是很有难度的,不过 做完 分割回文串 之后,本题就容易很多了
  • 第二题子集问题,就是收集树形结构中,每一个节点的结果。 整体代码其实和 回溯模板都是差不多的。
  • 第三题做了 40.组合总和II 和 78.子集 ,本题就是这两道题目的结合。
  • 28
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值