依靠递归进行树的建立,根据问题本身的一些特点进行 return
(即剪枝)。
总结了几道 leetcode 上常见的回溯的题目。
22. 括号生成
var generateParenthesis = function(n) {
//插入数量不超过n
//可以插入 ) 的前提是 ( 的数量大于 ) ——括号成对偶的硬性要求,因为每一个 ) 都需要一个( 进行匹配
//每次放 ( 或者 ) 都要考虑,
let result = []
function help(left, right, string){
if(left === n && right === n){
result.push(string)
return
}
if(left < n){
help(left + 1, right, string + '(')
}
if(right < left){
help(left, right + 1, string + ')')
}
}
help(0, 0, '')
return result
};
20. 有效的括号——非回溯
本题不是用回溯法,只是根据上一题的生成括号过程,我们可以反过来判断括号是否有效。
var isValid = function(s) {
//左边的全部压进去,右边的来匹配,匹配到左边的就,左边的就出栈,所以一定是都能匹配的
let len = s.length
if(len % 2 !== 0) return false
let stack = [] //匹配到就出栈,否则压栈
for(let i = 0; i < len; i++){
if(s[i] === '('){
stack.push(s[i])
}else if(s[i] === ')'){
let current = stack.pop()
if(current !== "(") return false
}else if(s[i] === '['){
stack.push(s[i])
}else if(s[i] === ']'){
let current = stack.pop()
if(current !== "[") return false
}else if(s[i] === '{'){
stack.push(s[i])
}else if(s[i] === '}'){
let current = stack.pop()
if(current !== "{") return false
}else{
return false
}
}
return stack.length === 0
};
93. 复原IP地址
var restoreIpAddresses = function(s) {
let result = []
function help(pre, n, arr){ //上一次插入的位置(pre之前),已经插入过的次数,当前的结果
if(n === 3){
let str = s.substring(pre)
let num = parseInt(str)
if(str.substring(0,1) === '0' && str.length > 1) return
if(str.length < 4 && num <= 255){
arr.push(str)
result.push(arr.join('.'))
}
return
}
for(let i = pre + 1; i < s.length && i - pre <= 3; i++){
let str = s.substring(pre, i)
let num = parseInt(str)
if(str.substring(0,1) === '0' && str.length > 1) continue //除非是0,否则不会由零开始
if(str.length < 4 && num <= 255){
help(i, n + 1, [...arr, str])
}
}
}
help(0, 0, [])
return result
};
78. 子集
每个元素都可以选择或者不选择 每一次递归,下标都要增加。
var subsets = function(nums) {
//回溯算法,父节点的情况
let len = nums.length
let result = []
function help(index, arr){ //index 为数组的下标
if(index === len - 1){
result.push(arr)
return
}
help(index + 1, [...arr])
help(index + 1, [...arr, nums[index + 1]])
}
help(-1, [])
return result
};
46. 全排列
**注意:数组拷贝修改,需要深拷贝,每一次递归使用的数组都不一样!
[...temp]
每一次将可挑选的范围缩小即可。
var permute = function(nums) {
let result = []
function help(remainNums, res){
let len = remainNums.length
if(len === 0) {
result.push(res)
}
for(let i = 0; i < len; i++){
let temp = [...remainNums] //数组拷贝修改,需要深拷贝!!!
let item = temp.splice(i, 1)[0] //切了返回被切掉的
help(temp, [...res, item])
// //不拷贝数组的做法
// let item = remainNums.splice(i, 1)[0]
// help(remainNums, [...res, item])
// remainNums.splice(i, 0, item)
}
}
help(nums, [])
return result
};
77. 组合
Cnk n中选k,每个都可以选或者不选,选完k个结束,到n了还没选完也结束。与子集的不同之处是,这里规定了选取的长度,而子集的长度任意。
var combine = function(n, k) {
// Cnk
let result = []
function help(currentNum, remain, arr){
if(currentNum > n) return
if(remain === 0){
result.push(arr)
return
}
help(currentNum + 1, remain, arr)
help(currentNum + 1, remain - 1, [...arr, currentNum + 1])
}
help(0, k, [])
return result
};
39. 数组总和
找到数组子集元素等于目标值的所有组合(下标单调增,达到去重的效果)
var combinationSum = function(candidates, target) {
//简单的回溯法会造成重复,
//当前的元素可以重复,但是后面的递归不能再用当前元素前面的元素!
//回溯法
let len = candidates.length
let result = []
function help(arr, cur, start){ //当前的结果,当前的和,当前可以使用元素的起始位置
if(cur > target){
return
}else if(cur === target){
result.push(arr)
}else{
for(let i = start; i < len; i++){
help([...arr, candidates[i]], cur + candidates[i], i)
}
}
}
help([], 0, 0)
return result
};
岛屿问题
这类问题使用 dfs 来解决。
网格的遍历容易重复遇到之前遍历过的点,所以要做标记。
695. 岛屿的最大面积
2层循环遍历每个点,遇到一个岛屿就能遍历这个岛的所有内容,并且全部变成2,避免重复。 注意边界返回值即可
var maxAreaOfIsland = function(grid) {
//遇到一个为1的点,就开始找到它周围为1的点,全部置为2,也就是让这个岛在后续的遍历中,不再重复遍历。
let row = grid.length
let col = grid[0].length
let result = 0
for(let i = 0; i < row; i++){
for(let j = 0; j < col; j++){
if(grid[i][j] === 1){
let a = area(i, j)
result = Math.max(result, a)
}
}
}
function area(i, j){
if(i < 0 || i > row - 1 || j < 0 || j > col -1){
return 0
}
if(grid[i][j] !== 1) return 0
grid[i][j] = 2
return 1
+ area(i - 1, j)
+ area(i, j - 1)
+ area(i + 1, j)
+ area(i, j + 1)
}
return result
};
200. 岛屿的数量
var numIslands = function(grid) {
//遇到一个为1的点,就开始找到它周围为1的点,全部置为2,也就是让这个岛在后续的遍历中,不再重复遍历。
let row = grid.length
if(row <= 0) return 0
let col = grid[0].length
let result = 0
for(let i = 0; i < row; i++){
for(let j = 0; j < col; j++){
if(grid[i][j] === '1'){ //遇到一个1,能够这个岛屿全部变成 2,即沉没
//这里也可以直接 result++,然后在area中对岛屿进行沉没
result += area(i ,j)
}
}
}
//每遇到一个岛就让它全部为2
function area(i, j){
if(i < 0 || i > row - 1 || j < 0 || j > col -1){
return 0
}
if(grid[i][j] !== '1') return 0
grid[i][j] = '2'
area(i - 1,j)
area(i ,j - 1)
area(i + 1,j)
area(i ,j + 1)
return 1
}
return result
};
463. 岛屿的周长
var islandPerimeter = function(grid) {
let row = grid.length
if (row === 0) return 0
let col = grid[0].length
let result = 0
for (let i = 0; i < row; i++) {
for (let j = 0; j < col; j++) {
if (grid[i][j] === 1) {
result = Math.max(help(i, j), result)
}
}
}
function help(i, j) {
//周长即遍历的过程中遇到边界或者水是次数
//遇到边界
if (i < 0 || j < 0 || i > row - 1 || j > col - 1) {
return 1
}
//遇到水
if (grid[i][j] === 0) {
return 1
}
if (grid[i][j] === 1) {
grid[i][j] = 2
let a = help(i - 1, j) +
help(i, j - 1) +
help(i + 1, j) +
help(i, j + 1)
return a
}
//2的情况
return 0
}
return result
};