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 条评论