代码随想录算法训练营第二十六天 | 39. 组合总和、40. 组合总和 II、131. 分割回文串

79 阅读3分钟

39. 组合总和

链接

文章链接

题目链接

第一想法

这道题如果不算去重的话很简单,按照三部曲写就可以了,但是现在的问题是需要去重,我的想法是利用指向标来解决问题,代码如下:

function combinationSum(candidates: number[], target: number): number[][] {
    let res:number[][]=[]
    candidates.sort((a,b)=>a-b) //非常重要的一步  只有排列好了之后才能完整的去重
    const foo=(arr: number[], target: number,sum:number,order:number)=>{
        if(sum==target){ //终止条件一
         res.push(Array.from(arr))
         return 
        }
         if(sum>target) return //终止条件2
         for(let i=0;i<candidates.length;i++){ //可以重复 所以都从0开始
             if(candidates[i]<order) continue//去重 很重要的一步  例如排列好后的结果为[2,2,3],[2,3,2].order是上层递归的元素,因为要求当前递归的元素要大于等于上层递归的元素,所以[2,3,2]这个是不符合要求的 被去掉
             arr.push(candidates[i])
             sum+=candidates[i]
             foo(arr,target,sum,candidates[i])
             sum-=candidates[i] //回溯
             arr.pop()//回溯
         }
    }
    foo([],target,0,0)
    return res
};

看完文章的想法

文章的想法确实简单,我是通过指向标来进行去重的,而文章中是利用startIndex进行去重的,同时也避免了使用nums.sort(),所以是比我更优的,同时也可以进行剪枝,因为如果sum>target的话还是进入了下一层递归,可以在for添加条件让其终止在本层,代码如下:

function combinationSum(candidates: number[], target: number): number[][] {
    let res:number[][]=[]
    const foo=(arr: number[], target: number,sum:number,startIndex:number)=>{
        if(sum==target){
         res.push(Array.from(arr))
         return 
        }
         if(sum>target) return 
         for(let i=startIndex;i<candidates.length;i++){
             if(sum+candidates[i]>target) continue
             arr.push(candidates[i])
             sum+=candidates[i]
             foo(arr,target,sum,i)//这里继续从i开始  因为可以重复
             sum-=candidates[i]
             arr.pop()
         }
    }
    foo([],target,0,0)
    return res
};

思考

这道题如果不考虑去重的话是比较简单的,但是要考虑去重的话,需要思考如何去重,自己的想法是利用排序+指向标,小于指向标的话就不进行递归了.文章用的是startIndex,用于去重,同时递归时从i开始,保证了一个元素可以重复使用.

40. 组合总和 II

链接

文章链接

题目链接

第一想法

这道题要求是每个数字只能用一次,所以需要startIndex用于防止一个数字用多次,同时也需要去重,如何去重?详细的在代码中展示

function combinationSum2(candidates: number[], target: number): number[][] {
    let res:number[][]=[]
    candidates.sort((a,b)=>a-b)//用于去重  关键一步
    const foo=(arr:number[],startIndex:number,candidates: number[], target: number,sum:number)=>{
        if(sum>target) return //终止
        if(sum==target){//终止
            res.push(Array.from(arr))
            return
        }
        for(let i=startIndex;i<candidates.length;i++){
            if(i>startIndex&&candidates[i]==candidates[i-1]) continue//去重 例如[1,1,2,3],target=3的情况 因为第一个1能出现[1,2]这个结果 所以就不需要第二个1出现[1,2]这个结果 这就需要在本层阻止第二个1进行递归,所以这里的条件为i>startIndex&&candidates[i]==candidates[i-1]用于阻止之后相同的数进入递归
            sum+=candidates[i]
            arr.push(candidates[i])
            foo(arr,i+1,candidates,target,sum)
            sum-=candidates[i]//回溯
            arr.pop()
        }
    }
    foo([],0,candidates,target,0)
    return res
};

看完文章后的想法

文章的想法和我的想法类似,思想就是在同一层去重,附一张图片:

image.png 如何在同一层去重就是一个难点,文章用了两种方法进行去重,一个是和我一样的,另一种使用数组去重,数组去重就是用是否使用过,如果是树枝去重就是used[i-1]==true,如果是树层去重的话就是used[i-1]==false,详细解释如这张图:

image.png 代码如下:

function combinationSum2(candidates: number[], target: number): number[][] {
    let res:number[][]=[]
    candidates.sort((a,b)=>a-b)
    const foo=(arr:number[],startIndex:number,candidates: number[], target: number,sum:number,used:boolean[])=>{
        if(sum>target) return
        if(sum==target){
            res.push(Array.from(arr))
            return
        }
        for(let i=startIndex;i<candidates.length;i++){
            if(candidates[i]==candidates[i-1]&&used[i-1]==false) continue
            sum+=candidates[i]
            arr.push(candidates[i])
            used[i]=true
            foo(arr,i+1,candidates,target,sum,used)
            used[i]=false
            sum-=candidates[i]
            arr.pop()
        }
    }
    let used=new Array(candidates.length).fill(false)
    foo([],0,candidates,target,0,used)
    return res
};

思考

这道题就是考的去重,我认为sort()+startIndex去重是比used去重好的,所占据的空间比较小,虽然used号理解,但是熟练运用的还是startIndex方法.

131. 分割回文串

链接

文章链接

题目链接

第一想法

这道题知道是用回溯法,但是不知道怎么用,所以就没写出来,没啥想法.

看完文章后的想法

寄,确实没想到,其实分割可以抽象出分组,例如这张图:

image.png 所以终止条件就很容易看出来了,当index>=str长度时就可以终止了,同时,要确定本层是回文串时才能继续向下层递归,代码如下:

function partition(s: string): string[][] {
    let res:string[][]=[]
    const foo=(str:string,startIndex:number,arr:string[])=>{
        if(startIndex>=str.length){//终止条件
            res.push(Array.from(arr))
            return
        }
        for(let i=startIndex;i<str.length;i++){
            if(isPalindrome(str,startIndex,i)){ //判断是否是回文串  只有回文串才继续向下递归
                let s:string=str.substring(startIndex,i+1)
                arr.push(s)
                foo(str,i+1,arr)
                arr.pop()
            }else continue;
        }
    }
    const isPalindrome=(str:string,start:number,end:number)=>{ //判断是否是回文
        for(;start<end;start++,end--){
            if(str[start]==str[end]) continue
            else return false
        }
        return true
    }
    foo(s,0,[])
    return res
};

思考

这道题难在如何把分割思想转换为分组思想,以及如何模拟分割.这道题如果能想到分组的方法,这道题就能转出来,分割是用startIndex来进行分割的,只有是回文子串才继续递归,所以不好想.

今日总结

今日总共三道题,耗时2.5小时,三道题主要涉及去重以及思想转换,第一道题去重比较简单,第二道题就需要考虑如何去重,第三道题对我来说是最难得,没有想法.看完文章后才恍然大悟,分割问题可以用分组的方法解决.第三道题得多多查看