leetcode算法刷题的第二十一天

1.leetcode 93.复原IP地址

题目链接

cpp 复制代码
class Solution {
public:
    vector<string> result;// 记录结果
    // 判断字符串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){
                return false;// 如果大于255了不合法
            }
        }
        return true;
    }
    // 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;// 不合法,直接结束本层循环
        }
    }
    vector<string> restoreIpAddresses(string s) {
        if(s.size()<4||s.size()>12) return result;
        backtracking(s,0,0);
        return result;
    }
};

思路总结:

这道题目相信大家刚看的时候,应该会一脸茫然。

其实只要意识到这是切割问题,切割问题就可以使用回溯搜索法把所有可能性搜出来。

切割问题可以抽象为树型结构。

最后就是在写一个判断段位是否是有效段位了。

主要考虑到如下三点:

段位以0为开头的数字不合法

段位里有非正整数字符不合法

段位如果大于255了不合法

在分割回文串中我列举的分割字符串的难点,这道题基本都覆盖了,而且本题还需要操作字符串添加逗号作为分隔符,并验证区间的合法性,可以说是分割回文串的加强版了,如果大家能把树形结构图画出来的话,应该就能很好的理解题意。

2.leetcode 78.子集

题目链接

cpp 复制代码
class Solution {
public:
    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();
        }
    }
    vector<vector<int>> subsets(vector<int>& nums) {
        backtracking(nums,0);
        return result;
    }
};

思路总结:

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

其实子集也是一种组合问题,因为它的集合是无序的,子集{1,2} 和 子集{2,1}是一样的。

那么既然是无序,取过的元素不会重复取,写回溯算法的时候,for就要从startIndex开始,而不是从0开始!

有同学问了,什么时候for可以从0开始呢?

求排列问题的时候,就要从0开始,因为集合是有序的,{1, 2} 和{2, 1}是两个集合,排列问题我们后续的博客就会讲到的。

在注释中,我们可以看到可以不写终止条件,因为我们本来就要遍历整棵树。有的人可能会担心不写终止条件会不会无限递归?并不会,因为每次递归的下一层就是从i+1开始的。

3.leetcode 90.子集II

题目链接

cpp 复制代码
class Solution {
public:
    vector<vector<int>> result;
    vector<int> path;
    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) {
        vector<bool> used(nums.size(),false);
        sort(nums.begin(),nums.end());//对于这种去重,我们先要对其进行排序
        backtracking(nums,0,used);
        return result;
    }
};

补充:本题也可以不使用used数组来去重,因为递归的时候下一个startIndex是i+1而不是0。如果要是全排列的话,每次要从0开始遍历,为了跳过已入栈的元素,需要使用used。

cpp 复制代码
class Solution {
public:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& nums,int startIndex){
        result.push_back(path);
        for(int i=startIndex;i<nums.size();i++){
            //而我们要对同一树层使用过的元素进行跳过
            if(i>startIndex&&nums[i]==nums[i-1]){//注意这里是i>startIndex
                continue;
            }
            path.push_back(nums[i]);
            backtracking(nums,i+1);
            path.pop_back();
        }
    }
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        sort(nums.begin(),nums.end());//去重需要排序
        backtracking(nums,0);
        return result;
    }
};

思路总结:这道题目和上一题的区别在于集合里面就已经有重复的元素了,而且求取的子集要去重。这里我们要理解什么是树层去重和树枝去重,这个很重要。注意:去重需要先对集合排序。

同一树层上重复取2 就要过滤掉,同一树枝上就可以重复取2,因为同一树枝上元素的集合才是唯一子集!如果之前的子集问题和去重问题都可以掌握的好,这道题目可以分分钟AC。

相关推荐
JuneXcy30 分钟前
循环高级(1)
c语言·开发语言·算法
流火无心1 小时前
mysql索引 底层数据结构与算法
数据结构·mysql·索引
Ka1Yan2 小时前
什么是策略模式?策略模式能带来什么?——策略模式深度解析:从概念本质到Java实战的全维度指南
java·开发语言·数据结构·算法·面试·bash·策略模式
绝无仅有2 小时前
Go Timer 面试指南:常见问题及答案解析
后端·算法·架构
地平线开发者3 小时前
开发者说|H-RDT:基于人类操作数据的跨本体机器人学习
算法·自动驾驶
biuyyyxxx5 小时前
Excel数组学习笔记
笔记·学习·算法
南莺莺5 小时前
//Q是一个队列,S是一个空栈,实现将队列中的元素逆置的算法。
数据结构·算法·链表·
闻缺陷则喜何志丹5 小时前
【分治法 BFS 质因数分解】P12255 [蓝桥杯 2024 国 Java B] 园丁|普及+
c++·算法·蓝桥杯·宽度优先·质因数分解·分治法
寒冬没有雪5 小时前
按对角线进行矩阵排序
c++·算法
Huangichin5 小时前
C++基础算法——贪心算法
c++·算法·贪心算法