0
点赞
收藏
分享

微信扫一扫

回溯递归总结

前言

题目

  1. 组合 *
  2. 组合总和 *
  3. 组合总和 II *
  4. 电话号码和字母的组合 *
  5. 括号生成 *
  6. 分割回文串 *
  7. 复原IP地址 *
  8. 子集 *
  9. 递增子序列 *
  10. 全排列 *
  11. 全排列II *

回溯组合题型

组合:组合
切割:分割回文串
子集:子集
排列:全排列
棋盘

基本思路

确定传参,返回值;确定终止条件;确定每层逻辑;尝试例子,走思路。

注意点

res.push([...item]),如果res.push(item),会有问题(共用一个堆内存)
辅助函数比较多,例:判断回文,递增,是否重复
如果有循环(BFS)来控制,是不会爆栈的,并且item单项长度最大不可能超过原数组长度。但是如果指定长度(小于原数组长度),就需要return来控制长度。

组合

var combine = function(n, k) {
    let res = []
    let item = []
    dfs(1,k)
    function dfs(startIndex,k) {
        if(item.length === k) {
            res.push([...item])
            return 
        }
        for(let i = startIndex;i<=n;i++) {
            item.push(i)
            dfs(i+1,k)
            item.pop()
        }
        
    }
    return res
}

组合总和

/**
 * @param {number[]} candidates
 * @param {number} target
 * @return {number[][]}
 */
var combinationSum = function(candidates, target) {
    // 用组合求
    let res = []
    let item = []
    let sum = 0
    function dfs(startIndex,target) {
        if(sum>=target) {
           if(sum===target) {
               res.push([...item])
           }
            return
        }
        // 这里和组合不同的是,组合取得是 <=, 这里是<, 如果取 = 了,下面就会push进一个不存在的元素
        // 组合取<, 是因为push的时候存在这个元素
        for(let i = startIndex;i<candidates.length;i++) {
           item.push(candidates[i])
           sum += candidates[i]
           dfs(i,target)
           item.pop()
           sum -= candidates[i]
        }
    }
    dfs(0,target)

    return res

};

组合总和 II

这题超时了,可能需要剪枝等操作降低时间复杂度,不去看了
思路也是老样子,注意点如下:

1. 不重复,递归就加1,dfs(i+1),上面组合总和是可以重复的就不加1,dfs(i)
2. 终止条件一定要注意
3. res.push([...item])要注意,不能直接 res.push(item)
4. set集合也有has方法,不过新增设置使用的是add,不像map用的set方法
/**
 * @param {number[]} candidates
 * @param {number} target
 * @return {number[][]}
 */
var combinationSum2 = function(candidates, target) {

    // 数组内去重:
    //  
    let res = []
    let item = []
    let sum = 0
    // let sumSet = new Set()
    let sumSet = []
    candidates = candidates.sort()
    function dfs(startIndex,target) {
        // 不可能等于
        if(sum>=target) {
            if(sum === target) {
                // if(!sumSet.has(item.toString())) {
                //     sumSet.add(item.toString())
                //     res.push([...item])
                // }
                if(!sumSet.includes(item.toString())) {
                    sumSet.push(item.toString())
                    res.push([...item])
                }
            }
            return
        }
        for(let i =startIndex; i<candidates.length; i++) {
            item.push(candidates[i])
            sum += candidates[i]
            dfs(i+1,target)
            // 确保去掉的值和减掉的值是同一个
            let pop = item.pop()
            sum -= pop
        }
    }
    dfs(0,target)
    return res
};

电话号码和字母的组合

这题稍微有点难度(有点复杂),不过问题也不大
注意点:

map的has、get、set方法
slice(n,m)是不改变原数组的,从n开始剪,剪到m,不包含m

思路:这题非常巧妙,2=>{‘abc’},3=>{‘def’},两个循环,第一个字符串变成’a’后,直接外层指针到3,3进入递归,获取到 ‘d’,进而字符串变成’ad’。两个数组的组合问题,可以思考这种做法。

var letterCombinations = function (digits) {
  // 为空特殊处理
  if (digits.length === 0) return [];
  let numMap = new Map([
    ['0', ''],
    ['1', ''],
    ['2', 'abc'],
    ['3', 'def'],
    ['4', 'ghi'],
    ['5', 'jkl'],
    ['6', 'mno'],
    ['7', 'pqrs'],
    ['8', 'tuv'],
    ['9', 'wxyz']
  ])

  let res =[]
  let item = ''
  function dfs(startIndex,k) {
      if(item.length===k) {
          res.push(item)
          return
      }
      for(let i = startIndex; i<digits.length; i++) {
          for(let j =0;j<numMap.get(digits[i]).length;j++) {
            item += numMap.get(digits[i])[j]
            dfs(i+1,k)
            item = item.slice(0,item.length-1)
          }
      } 

  }
  dfs(0,digits.length)
  return res

};

括号生成

这题难度相对来说比较大,如果第一次做可以做出来,那就是天才。
这题和上面组合那道题(第一题最大的区别是),组合问题一般是靠循环来移动指针:

[1,2,3,4],求这个的所有长度为2的集合

首先指针在1,那么递归后索引+1,新的递归 新的指针指向2,然后形成[1,2]
执行完后,清除尾部元素,变为[1],循环继续,下一个循环,形成[1,3],到[1,4]
变成[1],变成[],进入下一个循环[2]

括号生成这题:

一旦闭括号数量比开括号数量多,终止递归。
因为这种情况就有问题,一旦闭括号比开括号多了,那说明绝对有一个闭括号是配不上开括号的
所以 if(close>open) return

如果开括号或闭括号的数量大于3了,那也是有问题的
所以 if(item.length>3) return

如果长度恰好等于3的双倍6,那就返回一个单项
所以
if(item.length === 2*n) {
    res.push(item)
    return
}
还有如果我们按照传统方法做,我们该遍历谁呢?就只能想想双dfs(),这题真的就只能靠印象了。

生死有命,富贵在天

/**
 * @param {number} n
 * @return {string[]}
 */
var generateParenthesis = function(n) {

    
    let res = []
    function dfs(n,item,open,close) {
        if(open>n || close>open) return
        if(item.length === 2*n) {
            res.push(item)
            return
        }
        dfs(n,item+'(',open+1,close)
        dfs(n,item+')',open,close+1)
    }
    dfs(n,'',0,0)

    return res
};

分割回文串

这题不是人做的

/**
 * @param {string} s
 * @return {string[][]}
 */
var partition = function(s) {

    function isback(str) {
        for(let i =0;i<str.length/2;i++) {
            if(str[i]!=str[str.length-1-i]) {
                return false
            }
        }
        return true
    }

    let res = []
    let item = []

    function dfs(startIndex) {
        if(startIndex>=s.length) {
            res.push([...item])
        }
        for(let i =startIndex;i<s.length;i++) {
            let str = s.substr(startIndex,i-startIndex+1)
            if(isback(str)) {
               item.push(str) 
            }else{
                continue
            }
          dfs(i+1)
          item.pop()
        }
    }
    dfs(0)

    return res
   
};

复原IP地址

和上面那题非常像

var restoreIpAddresses = function(s) {
    const len = s.length;
    if (s === '') return [];
    if (len < 4 || len > 12) return [];

    const res = [];
    const path = [];
    backtracking(0);
    return res;

    function backtracking(start) {
        if (path.length === 4) {
            if (start >= len) {
                res.push(path.join('.'));
            }
            return;
        }

        for (let i = start; i < len; i++) {
            const str = s.slice(start, i + 1);
            if (isValid(str)) {
                path.push(str);
                backtracking(i + 1);
                path.pop();
            } else break;
        }
    }

    function isValid(str) {
        if (str.length > 3) return false;
        if (parseInt(str, 10) > 255) return false;
        if (str.length > 1 && str[0] === '0') return false;
        return true;
    }
};

子集

有很多回溯题,都没有终止return,因为有循环可以终止

/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var subsets = function(nums) {

    let res = []
    let item = []
    res.push([])
    function dfs(startIndex,nums){
        if(item.length) {
            res.push([...item])
            // return
        }
        for(let i=startIndex;i<nums.length;i++) {
            item.push(nums[i])
            dfs(i+1,nums)
            item.pop()
        }
    }
    dfs(0,nums)
    return res

};

递增子序列

递归用到的辅助函数比较多,去重,递增,回文,这些辅助函数都比较常用

/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var findSubsequences = function(nums) {
    // 只需要判断是不是递增就可以了
    // 还需要去重,用set
    let res =[]
    let item = []
    function isIncrease(item) {
        for(let i =0;i<item.length;i++) {
            if(item[item.length-1]<item[item.length-2]) return false
        }
        return true
    }
    let set = new Set()
    function removeRedun(item) {
        let str = item.join('')
        if(set.has(str)) {
            return false
        }else {
            set.add(str)
            return true
        }
    }
    function dfs(startIndex,nums) {
        if(item.length>1 && isIncrease(item) && removeRedun(item)) {
            res.push([...item])
        }
        for(let i =startIndex;i<nums.length;i++) {
             item.push(nums[i])
             dfs(i+1,nums)
             item.pop()
        }

    }
    dfs(0,nums)
    return res
};

全排列

排列问题需要一个 第三方数组 来记录状态

求[1,2]的全排列, 如果是求组合的话是 [1] [1,2] [2]

我们不需要startIndex来记录状态,因为这样无论如何都得不到[2,1],不可能往前找,只会往后找。

var permute = function(nums) {
    // 针对全排列
    // [1,2]会有[1,2]和[2,1]两种情况,所以,如果我们再使用startIndex+1的话,组合只会向后取
    // 就算我们使用startIndex,也不可能向前取
    // 所以我们需要额外的一个数组来辅助我们

    let res = []
    let item = []
    function dfs(used,nums) {
        if(nums.length === item.length) {
            res.push([...item])
            // 不需要return,绝对小于等于最长长度
        }
        for(let i =0;i<nums.length;i++) {
             if(used[i]) continue
             used[i] = true
             item.push(nums[i])
             dfs(used,nums)
             used[i] = false
             item.pop()
        }   
    }

    dfs([],nums)

    return res

};

全排列II

全排列+辅助函数

var permuteUnique = function(nums) {
    // 这题就是全排列加去重操作
    let res = []
    let item = []

    let numsSet = new Set()
    // 辅助函数
    function isRepeat(item) {
        let str = item.join('')
        if(numsSet.has(str)) {
            return false
        }else {
            numsSet.add(str)
            return true
        }
    }

    function dfs(used,nums) {
        if(item.length === nums.length && isRepeat(item)) {
            res.push([...item])
        }
        for(let i =0;i<nums.length;i++) {
            if(used[i]) continue
            used[i] = true
            item.push(nums[i])
            dfs(used,nums)
            item.pop()
            used[i] = false
        }

    }

    dfs([],nums)
    return res

};

总结

首先确定是组合,还是子集,还是排列问题
确定递归函数传参,及返回值和终止递归
确定是不是需要BFS挪动指针
确定单层的递归逻辑
确定是不是需要辅助函数
多试用例

举报

相关推荐

0 条评论