第二十八天| 93.复原IP地址 、78.子集、90.子集II

Leetcode 93.复原IP地址

题目链接:93 复原IP地址

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

  • 例如:"0.1.2.201" 和 "192.168.1.1" 是 有效 IP 地址,但是 "0.011.255.245""192.168.1.312" 和 "[email protected]" 是 无效 IP 地址。

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

思考一:回溯法。先定义结果集result,再考虑回溯函数:

函数参数含义
参数 含义
s 题干给定字符串
startIndex 下一层循环搜索的起始位置
path 保存的部分IP
count 分隔符的个数

终止条件:count个数满四个说明path已经可以组成IP,再判断startIndex是否大于字符串长度,若是则将path中最后一个分隔符去除后加入结果集result,否则说明字符串中数字未全用到返回。

单层搜索逻辑:从startIndex开始循环取出字符串里的数并判断组成的子IP是否满足条件,若满足则递归处理,处理后再回溯。(特殊考虑:若此次搜索首个数字为0则该循环只处理一次)

代码:

class Solution {
public:
    vector result;

    void backtracking(const string& s, int startIndex, string path, int count) {
        if (count == 4) {     //分隔符.的个数满足条件
            if (startIndex >= s.size()) {       //字符串内数字全用到
                path.resize(path.size() - 1);       //去除最后面的分隔符
                result.push_back(path);
            }
            return;
        }

        int num = 0;
        for (int i = startIndex; i < s.size(); i++) {
            num = num * 10 + (s[i] - '0');
            if (i > startIndex && s[startIndex] == '0') break;      //首个数字为0只处理一次
            if (num > 255)  break;      //已经超过限定值
            count++;
            backtracking(s, i + 1, path + to_string(num) + ".", count);
            count--;
        }
    }

    vector restoreIpAddresses(string s) {
        result.clear();
        string path = "";
        backtracking(s, 0, path, 0);
        return result;
    }
};

思考二:与思考一的区别:直接在s中修改并把判断子IP是否合理单独设计函数处理。

代码: 

class Solution {
private:
    vector result;// 记录结果
    // startIndex: 搜索的起始位置,pointNum:添加逗点的数量
    void backtracking(string& s, int startIndex, int pointNum) {
        if (pointNum == 3) { // 逗点数量为3时,分隔结束
            // 判断第四段子字符串是否合法,如果合法就放进result中
            if (isValid(s, startIndex, s.size() - 1)) {
                result.push_back(s);
            }
            return;
        }
        for (int i = startIndex; i < s.size(); i++) {
            if (isValid(s, startIndex, i)) { // 判断 [startIndex,i] 这个区间的子串是否合法
                s.insert(s.begin() + i + 1 , '.');  // 在i的后面插入一个逗点
                pointNum++;
                backtracking(s, i + 2, pointNum);   // 插入逗点之后下一个子串的起始位置为i+2
                pointNum--;                         // 回溯
                s.erase(s.begin() + i + 1);         // 回溯删掉逗点
            } else break; // 不合法,直接结束本层循环
        }
    }
    // 判断字符串s在左闭又闭区间[start, end]所组成的数字是否合法
    bool isValid(const string& s, int start, int end) {
        if (start > end) {
            return false;
        }
        if (s[start] == '0' && start != end) { // 0开头的数字不合法
                return false;
        }
        int num = 0;
        for (int i = start; i <= end; i++) {
            if (s[i] > '9' || s[i] < '0') { // 遇到非数字字符不合法
                return false;
            }
            num = num * 10 + (s[i] - '0');
            if (num > 255) { // 如果大于255了不合法
                return false;
            }
        }
        return true;
    }
public:
    vector restoreIpAddresses(string s) {
        result.clear();
        if (s.size() < 4 || s.size() > 12) return result; // 算是剪枝了
        backtracking(s, 0, 0);
        return result;
    }
};

Leetcode 78.子集

题目链接:78 子集

题干:给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

思考:第二十八天| 93.复原IP地址 、78.子集、90.子集II_第1张图片

由上图可以看出,收集每个搜索的节点子集即可 。此题也不需要终止条件。

代码:

class Solution {
public:
    vector> result;
    vector node;

    void backtracking(vector& nums, int startIndex) {
        result.push_back(node);     //收集子集,包括空集
        for (int i = startIndex; i < nums.size(); i++) {
            node.push_back(nums[i]);
            backtracking(nums, i + 1);
            node.pop_back();
        }
    }

    vector> subsets(vector& nums) {
        result.clear();
        node.clear();
        backtracking(nums, 0);
        return result;
    }
};

Leetcode 90.子集II

题目链接:90 子集II

题干:给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。

思考:与上题的区别:数组中的数字可重复。因此同层搜索逻辑中重复数字只处理一次。所以先将数组的元素进行排序,其次在循环中除首个(startIndex)处理的数字外其余数字均判断是否与前一位数字相同,若相同则说明同一数字重复出现跳过后序处理进行遍历下一个数字。

代码:

class Solution {
public:
    vector> result;
    vector node;

    void backtracking(vector& nums, int startIndex) {
        result.push_back(node);     //收集子集,包括空集
        for (int i = startIndex; i < nums.size(); i++) {
            if (i > startIndex && nums[i] == nums[i - 1])       //同层搜索中重复的数字只处理一次
                continue;
            node.push_back(nums[i]);
            backtracking(nums, i + 1);
            node.pop_back();
        }
    }
    vector> subsetsWithDup(vector& nums) {
        result.clear();
        node.clear();
        sort(nums.begin(),nums.end());
        backtracking(nums, 0);
        return result;
    }
};

自我总结:

  • 分清子集问题和组合问题、分割问题的的区别:

    • 子集是收集树形结构中树的所有节点的结果。

    • 组合问题、分割问题是收集树形结构中叶子节点的结果。

你可能感兴趣的:(代码随想录算法训练营,leetcode,算法)