代码随想录算法训练营day24

代码随想录算法训练营

—day24


前言

今天是算法营的第23天,希望自己能够坚持下来!
今日任务:
● 93.复原IP地址
● 78.子集
● 90.子集II


一、93.复原IP地址

题目链接
文章讲解
视频讲解

在这里插入图片描述

首先需要知道合法的IP的要求:
1.一共有个3逗点
2.每个分割区间不能是0开头的数字(可以单个0)
3.每个分割区间不能含有非数字符号
4.每个分割区间的数字不能大于255

思路:

  1. 递归函数的参数和返回值:参数:startIndex一定是需要的,因为不能重复分割,记录下一层递归分割的起始位置。还需要一个pointNum来记录逗点的数量
  2. 终止条件:当逗点数量=3的时候终止,此时还需要验证一下第四段是否合法,如果合法就加入到结果集里
  3. 单层递归的逻辑:for循环去遍历每个可能的分割点,判断分割出来的字符串是否满足ip要求,不满足则直接break,因为如果当前段不合法,那么后面的分割也不会合法
  4. 剪枝:IP 地址的长度必须在4到12之间(包括点和数字)
class Solution {
public:
    vector<string> result;

    void backtracking(string& s, int startIndex, int pointNum) {
        if (pointNum == 3) { //逗点数量为3,分割结束
            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+1,但是因为插入了逗点所以+2
                s.erase(s.begin() + i + 1); // 回溯,删掉逗点
                pointNum--;
            } else break;// 不合法则直接结束本层循环
                // 因为如果当前段不合法,那么后面的分割也不会合法
        }
    }

    // 判断子串是否合法的辅助函数
    bool isValid(const string& s, int start, int end) {
        if (start > end) return false;

        //第一位不能为0
        if (s[start] == '0' && start != end) return false;

        int num = 0;
        // 检查是否有非数字字符,并且数字不能大于255
        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) return false;
        }

        return true;
    }

    vector<string> restoreIpAddresses(string s) {
        result.clear();

        if (s.size() < 4 || s.size() > 12) return result; // 剪枝:IP 地址的长度必须在4到12之间(包括点和数字)
        backtracking(s, 0, 0);
        return result;
    }
};

二、78.子集

题目链接
文章讲解
视频讲解

  • 与组合和分割问题不同,组合和分割问题的收集结果是在叶子节点,而子集是在每个节点都要收集结果。
  • 不过子集也是一种组合问题,因为它的集合是无序的,子集{1,2} 和 子集{2,1}是一样的。
  • 所以取过的元素不会重复取,写回溯算法的时候,for就要从startIndex开始,而不是从0开始。

在这里插入图片描述

思路:

  1. 递归函数参数:全局变量数组path为子集收集元素,二维数组result存放子集组合。(也可以放到递归函数参数里),还有startIndex。
  2. 终止条件:startIndex >= nums.size,其实这里不写也可以,因为startIndex >= nums.size(),本层for循环本来也结束了
  3. 单层处理逻辑:求取子集问题,不需要任何剪枝!因为子集就是要遍历整棵树。

代码如下:

class Solution {
public:
    vector<int> path;
    vector<vector<int>> retult;

    void backtracking(vector<int>& num, int startIndex) {
        retult.push_back(path); //每次递归都需要将path放入结果集
        //终止节点在这里是因为在叶子节点时,递归前放入了最后的元素,需要将path放入结果集
        if (startIndex >= num.size()) return; //其实这里不写也可以,因为for循环的终止条件也是>num.size()

        for (int i = startIndex; i < num.size(); i++) {
            path.push_back(num[i]);
            backtracking(num, i + 1);
            path.pop_back();
        }
    }

    vector<vector<int>> subsets(vector<int>& nums) {
        path.clear();
        retult.clear();
        
        backtracking(nums, 0);
        return retult;
    }
};

三、90. 子集 II

题目链接
文章讲解
视频讲解

这道题目和78.子集 (opens new window)区别就是集合里有重复元素了,而且求取的子集要去重。去重的思路跟40.组合总和II是一样的,使用一个used数组去标记使用过的元素。

在这里插入图片描述
同一树层上重复取2 就要过滤掉,同一树枝上就可以重复取2。

思路:

  1. 递归函数的参数:传入字符串s,遍历索引startIndex
  2. 终止条件:startIndex>=nums.size()
  3. 单层递归的逻辑:因为有重复的元素,所以另一个结果如果仍然以相同的元素开头就会重复。用used数组判断,对同一树层使用过的元素进行跳过
class Solution {
public:
    vector<int> path;
    vector<vector<int>> result;

    void backtracking(vector<int>& nums, int startIndex, vector<bool>& used) {
        result.push_back(path);

        if (startIndex >= nums.size()) return;

        for (int i = startIndex; i < nums.size(); i++) {
            // used[i - 1] == true,说明同一树枝candidates[i - 1]使用过
            // used[i - 1] == false,说明同一树层candidates[i - 1]使用过
            // 而我们要对同一树层使用过的元素进行跳过
            if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) continue;

            path.push_back(nums[i]);
            used[i] = true;
            backtracking(nums, i + 1, used);
            used[i] = false;
            path.pop_back();
        }
    }

    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        path.clear();
        result.clear();
        vector<bool> used(nums.size(), false);

        sort(nums.begin(), nums.end()); //去重需要排序
        backtracking(nums, 0, used);
        return result;
    }
};

也可以用set来去重

class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& nums, int startIndex) {
        result.push_back(path);
        unordered_set<int> uset;
        for (int i = startIndex; i < nums.size(); i++) {
            if (uset.find(nums[i]) != uset.end()) {
                continue;
            }
            uset.insert(nums[i]);
            path.push_back(nums[i]);
            backtracking(nums, i + 1);
            path.pop_back();
        }
    }

public:
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        result.clear();
        path.clear();
        sort(nums.begin(), nums.end()); // 去重需要排序
        backtracking(nums, 0);
        return result;
    }
};

总结

1.IP 地址的长度必须在4到12之间,每段不能以0开头,不能有数字以外的字符,不能超过255
2.分割和组合问题是在叶子节点收集结果,而集合是在每个节点收集结果。
3.树层去重的话,可以用used去重,也可以用set去重

明天继续加油!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值