代码随想录算法训练营第二十七天 | 93. 复原 IP 地址、78. 子集、190. 子集 II

77 阅读3分钟

93. 复原 IP 地址

链接

文章链接

题目链接

第一想法

这道题第一时间没想到,后来思考了一下,想到了昨天做的分割回文串,然后就有了思路,这道题我感觉难在条件的判断上,总共有以下几个需要进行判断:

一.终止条件是什么:

  1. 和回文串一样如果分割位置startIndex大于等于字符串的长度就终止
  2. 如果存储片段的arr.length>4,说明一定不能组成ip

二.不符合规则的ip有哪几种

  1. 单个字段长度大于3的就不符合
  2. 如果长度大于1但是含有前导0的
  3. 单个字段数值大于255的

代码如下:

function restoreIpAddresses(s: string): string[] {
    let res:string[]=[]
    const foo=(arr:string[],startIndex:number,s:string)=>{
        if(arr.length>4) return
        if(startIndex>=s.length){
            if(arr.length===4) res.push(arr.join("."))
            return
        }
        for(let i=startIndex;i<s.length;i++){
            if(i-startIndex>=3) continue//单个字段长度大于3的就不符合
            let str:string=s.substring(startIndex,i+1)
            if(str.length>1&&str[0]=="0") continue//长度大于1但是含有前导0的
            if(Number(str)>255) continue//单个字段数值大于255的
            arr.push(str)
            foo(arr,i+1,s)
            arr.pop()//回溯
        }
    }
    foo([],0,s)
    return res
};

看完文章后的想法

文章中用的思路和我的是一样的,但是文章是直接用的是字符串,而我用的是数组,本质是一样的,想看字符串写法的可以去代码随想录中查看,我上面的代码是可以优化的,因为i-startIndex>=3之后是肯定不成立的,所以可以把循环条件改一下:

for(let i=startIndex;i<Math.min(s.length,startIndex+3);i++){//这里进行缩减一下  
            let str:string=s.substring(startIndex,i+1)
            if(str.length>1&&str[0]=="0") continue
            if(Number(str)>255) continue
            arr.push(str)
            foo(arr,i+1,s)
            arr.pop()
 }

思考

如果昨天没有做分割回文串的话,今天这道题应该是很难想到的,由于昨天做过回文串,所以这道题就比较容易好像的,其中比较难的是如何去除不符合条件ip地址,罗列一下就很清晰了.

78. 子集

链接

文章链接

题目链接

第一想法

这道题看到的时候觉得是简单的,因为只需要把终止条件去掉,因为这回需要的是过程量,不只是结果量,代码如下:

function subsets(nums: number[]): number[][] {
    let res:number[][]=[]
    const foo=(nums:number[],arr:number[],startIndex:number)=>{
        res.push(Array.from(arr))
        for(let i=startIndex;i<nums.length;i++){
            arr.push(nums[i])
            foo(nums,arr,i+1)
            arr.pop()
        }
    }
    foo(nums,[],0)
    return res
};

看完文章后的想法

文章把这道题讲的很通透,如果把分组、分割、子集问题看成一棵树的话,分组和分割都是收集叶子节点,而子集问题是收集所有的节点,例如这张图:

image.png 为了防止一个数用了多次,所以还是用startIndex来控制开始遍历的位置,这道题文章中和我写的是一样的,这里就不放代码了,有兴趣的可以查看代码随想录.

思考

这道题其实就是一道标准的回溯模版题,不一样的是以前做的分割和分组的题都是收集叶子节点,而子集收集的是所有节点而已,就这一个差别而已.

90. 子集 II

链接

文章链接

题目链接

第一想法

看到之后知道是要树层去重的,由于之前做过组合总和II这道题,所以这道题的去重很容易就写出来了

function subsetsWithDup(nums: number[]): number[][] {
    let res:number[][]=[]
    const foo=(arr:number[],nums:number[],startIndex:number)=>{
        res.push(arr.slice())
        for(let i=startIndex;i<nums.length;i++){
            if(i>startIndex&&nums[i]==nums[i-1]) continue//树层去重 如果元素相同的话  当前元素和前一个相同的元素肯定会产生结果,所以当前元素就不用继续递归了
            arr.push(nums[i])
            foo(arr,nums,i+1)
            arr.pop()
        }
    }
    nums.sort((a,b)=>a-b)//对于去重关键的一步
    foo([],nums,0)
    return res
};

看完文章后的想法

文章也是说和组合去重II是一样的,所以还是用used数组和我刚刚写的两种方法来,这里就不多加叙述了,文章和我写的是类似的.

今日总结

今天总共三道题,一道分割两道子集,个人觉得分割还是比较难做的,子集感觉套模板+去重就可以秒杀大部分题了,今日耗时2小时,今天比较简单.