代码随想录算法训练营:24/60

非科班学习算法day24 | LeetCode93:复原IP地址 ,Leetcode78:子集 ,Leetcode90: 子集||


介绍

包含LC的几道题目,还有相应概念的补充。

相关图解和更多版本:

代码随想录 (programmercarl.com)https://programmercarl.com/#%E6%9C%AC%E7%AB%99%E8%83%8C%E6%99%AF


一、LeetCode题目

1.LeetCode93:复原ip地址 

题目链接:93. 复原 IP 地址 - 力扣(LeetCode)

题目解析

       理解角度很重要,我们可以将问题化归,将其理解为划分类似的做法,不过这次要将划线(‘.’)插入到原始字符串,当然这里也可以仍然利用路径来做,不过要注意的更多了,后面仅仅贴出尝试结果。不过哪种做法都需要一个辅助函数判断字符串是否符合地址要求,就和判断是否是回文串一样,极大的帮助我们代码分块,理清楚逻辑。

 c++代码如下:

class Solution {
public:
    // 类似分割问题,不过需要插入新的字符

    // 创建结果
    vector<string> res;

    // 判断合法字符串
    bool isValid(string s, int start, int end) {
        // 不满足情况
        // 1.没有在0到255

        // 2.不能有前导0
        if (start > end)
            return false;
        if (s[start] == '0' && start != end)
            return false;
        int num = 0;
        for (int i = start; i <= end; i++) {
            if (s[i] < '0' || s[i] > '9')
                return false;
            num = num * 10 + (s[i] - '0');
            if (num > 255)
                return false;
        }
        return true;
    }
    // 回溯函数
    void backtracking(string& s, int index, int pointnum) {
        // 终止条件
        if (pointnum == 3) {
            if (isValid(s, index, s.size() - 1)) {
                res.push_back(s);
            }
            return;
        }

        for (int i = index; i < s.size(); ++i) {
            if (isValid(s, index, i)) {
                s.insert(s.begin() + i + 1, '.');
                pointnum++;
                backtracking(s, i + 2, pointnum);
                pointnum--;
                s.erase(s.begin() + i + 1);
            } else
                break;
        }
    }
    vector<string> restoreIpAddresses(string s) {
        backtracking(s, 0, 0);
        return res;
    }
};

注意点1:最重要的i+2,因为现在引用字符串已经加入了一个点。

注意点2:引入了点计数,帮助我们判断什么时候达到中止条件,中止添加结果之前需要检查第四部分是否符合我们的地址要求!

注意点3:这个辅助函数是对字符串很经典的操作,多写多练

用路径的c++代码:

class Solution {
public:
    // 创建接受结果
    vector<string> res;
    // 创建路径
    string path;

    // 创建检验IP地址部分是否合法的函数
    bool isValidPart(const string& s) {
        if (s.empty() || s.size() > 3) return false;
        if (s.front() == '0' && s.size() > 1) return false; // 0开头的多位数不合法
        int val = stoi(s);
        return val >= 0 && val <= 255;
    }

    // 创建回溯函数
    void backtracking(string s, int startIndex, int partCount) {
        if (partCount == 4) {
            if (startIndex == s.size()) {
                res.push_back(path);
            }
            return;
        }

        for (int len = 1; len <= 3 && startIndex + len <= s.size(); ++len) {
            string curPart = s.substr(startIndex, len);
            if (isValidPart(curPart)) {
                if (partCount != 0) {
                    path += ".";
                }
                path += curPart;
                backtracking(s, startIndex + len, partCount + 1);
                path.resize(path.size() - curPart.size());
                if (partCount != 0) {
                    path.pop_back(); // 移除最后一个"."
                }
            }
        }
    }

    vector<string> restoreIpAddresses(string s) {
        backtracking(s, 0, 0);
        return res;
    }
};

 2.Leetcode78:子集 

题目链接:78. 子集 - 力扣(LeetCode)

题目解析

       子集问题和组合问题最大的区别就是:组合需要全部元素,也就是说组合收集的是叶子节点,而子集问题需要的是所有的可能。

 C++代码如下: 

class Solution {
public:
    // 子集问题最重要的是收获包括中间节点
    // 结果
    vector<vector<int>> res;
    // 路径
    vector<int> path;
    // 回溯函数-还是需要index,因为要的是组合不是排列
    void backtracking(vector<int>& nums, int index) {
        // 添加结果
        res.push_back(path);
        // 中止
        for (int i = index; i < nums.size(); i++) {
            path.push_back(nums[i]);
            backtracking(nums, i + 1);
            path.pop_back();
        }
    }
    vector<vector<int>> subsets(vector<int>& nums) {
        backtracking(nums, 0);
        return res;
    }
};

 注意点1:这里需要没有显式的写中止条件,其实就是中止在for循环全部结束之后,所以可以省略中止条件。

注意点2:这里需要在进入递归时就将结果收集,而不是判断结果之后收集,可以借助空集来理解这个问题。

3.Leetcode90:子集||

题目链接:90. 子集 II - 力扣(LeetCode)

题目解析

       子集的进阶,多了一个条件就是数组内部存在重复的元素,这就和之前在组合遇到的去重很像,就是在树枝去重。其他的部分就和子集的算法没有区别。

C++代码如下:

class Solution {
public:
    // 和子集的区别就是有重复元素-树层去重
    // 结果
    vector<vector<int>> res;
    // 路径
    vector<int> path;

    // 回溯函数
    void backtracking(vector<int>& nums, int index, vector<bool>& used) {
        // 收集结果
        res.push_back(path);

        for (int i = index; i < nums.size(); i++) {
            // 树层去重
            if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) {
                continue;
            }
            path.push_back(nums[i]);
            used[i] = true;
            backtracking(nums, i + 1, used);
            path.pop_back();
            used[i] = false;
        }
    }
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        // 初始化usd数组-因为要借助nums所以没有放在全局
        vector<bool> used = vector<bool>(nums.size(), false);
        sort(nums.begin(), nums.end());
        backtracking(nums, 0, used);
        return res;
    }
};

注意点1:去重一定要将数组排序!

使用set的C++代码如下:

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:学习了一下使用set的方法,set去重的逻辑也比较直接,每一次检查在设定的set中是否有之前添加的相同元素,如果有就跳过,没有就将其加入然后执行递归。

注意点2:set的find返回的是迭代器位置!

总结


打卡第24天,坚持!!!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值