我们一起搞回溯(1)
其实回溯算法中很关键的一个问题是递归。我们都知道递归,也看得懂递归,但有的时候在实际操作过程中却不知道怎么使用,有时候还会被搞晕。那我们就先来搞清楚递归。首先我们写递归,首先要明白函数要做什么。第二我们要找到递归结束的条件。第三我们就是要找到函数的等价关系式。也就是我们要不断的缩小参数的范围。缩小后,我们通过辅助变量或者操作,将原函数的结果保存。递归中最难的就是找到函数的等价关系式。我们通过多接触几道题,然后熟悉起来。
// https://leetcode.cn/problems/unique-paths/
/**
* @param {number} m
* @param {number} n
* @return {number}
*/
var uniquePaths = function (m, n) {
let arr = new Array()
for (let i = 0; i < m; i++) {
arr[i] = new Array()
for (let j = 0; j < n; j++) {
arr[i][j] = 0
}
}
var dfs = function (i, j) {
if (i == m - 1 || j == n - 1) {
return 1
}
if (arr[i][j]) {
return arr[i][j]
}
arr[i][j] = dfs(i + 1, j) + dfs(i, j + 1)
return arr[i][j]
}
return dfs(0, 0)
}
function test() {
console.log('res', uniquePaths(7, 3))
}
我们先从最简单的方法来,一步一步理解递归。举例,对于一个 1+2+3+...+100 的程序怎么写。
// 如果for循环来写
let sum = 0
for (let i = 1; i < 101; i++) {
sum += i
}
return sum
// 递归写法
var f = function (n) {
if (n == 1) {
return 1
}
return n + f(n - 1)
}
// 数组内和的相加。
var f = function (arr) {
if (arr.length == 0) {
return 0
}
return arr[0] + f(arr.slice(1))
}
//非递归写法
var f = function (arr) {
let n = arr.length
if (n == 0) {
return 0
}
let res = 0
for (let i = 0; i < n; i++) {
res += arr[i]
}
return res
}
如果按照一个已经写好的递归函数去尝试理解递归的工作原理的话,会比较头痛,但首先我们先画一张图来展示递归过程发生了什么。
f(5) 15
5+f(4) 5+4+3+2+1=15
4+f(3) 4+3+2+1
3+f(2) 3+2+1
2+f(1) 2+1
1 1
我们在写一个简单的递归常见算法。阶乘问题。
var f = function (n) {
if (n == 1) {
return 1
}
return n * f(n - 1)
}
//假如你已经开始混乱的时候。我们可以拿一个不用递归的写法
var f = function (n) {
let r = n
while (n >= 1) {
n = n - 1
r *= n
}
return n
}
// 斐波那契 1,1,2,3,5,8
var f = function (n) {
var cf = function (first, second, n) {
if (n > 0) {
if (n == 1) {
return first
} else if (n == 2) {
return second
} else if (n == 3) {
return first + second
}
return cf(second, first + second, n - 1)
}
return -1
}
cf(1, 1, n)
}
// 不用递归
var f = function (n) {
if (n == 1 || n == 2) {
return 1
}
let result = -1
let first = 1
let second = 1
for (let i = 3; i <= n; i++) {
result = first + second
first = second
second = result
}
return result
}
差不多理解了吧。所以假如我们把这个当作公式的话。我们就记住三点。找出结束的条件,找出方法的用处,找出如何缩小范围。
那接下来让我们开始回溯吧。回溯算法其实和递归很相像。不过回溯过程有自我判断。
//lettcode 22
// 题目的思路是判断左右所剩的数量,当左右剩余,继续做选择。
// 当右括号大于左括号,选择右括号。
// 出口,构建的长度是2n。
/**
* @param {number} n
* @return {string[]}
*/
var generateParenthesis = function (n) {
let arr = []
var backtracking = function (l, r, str) {
if (str.length == 2 * n) {
arr.push(str)
return
}
if (l > 0) {
backtracking(l - 1, r, str + '(')
}
if (r > l) {
backtracking(l, r - 1, str + ')')
}
}
backtracking(n, n, '')
return arr
}
// leetcode 46
// 解题思路 回溯终止条件,路径长度和nums一致。假如路径有循环值,过,没有加入。
var permute = function (nums) {
if (nums.length <= 1) return nums
let arr = []
var backtracking = function (n) {
if (n.length == num.length) {
arr.push(n)
return
}
num.forEach((item) => {
if (n.include(item)) return
backtracking([...n, item])
})
}
backtracking([])
return arr
}
#leetcode 78
// 天啊。这种写法
/**
* @param {number[]} nums
* @return {number[][]}
*/
var subsets = function (nums) {
let res = []
let subset = []
function dfs(i) {
if (i >= nums.length) {
res.push(subset.slice())
return;
}
subset.push(nums[i])
dfs(i + 1)
subset.pop()
dfs(i + 1)
}
dfs(0)
return res
};
/** 47
* @param {number[]} nums
* @return {number[][]}
*/
var permuteUnique = function (nums) {
let res = [],
perm = [],
count = {}
for (let i = 0; i < nums.length; i++) {
if (count.hasOwnProperty(nums[i])) {
count[nums[i]] += 1
} else {
count[nums[i]] = 1
}
}
function dfs() {
if (perm.length == nums.length) {
res.push(perm.slice())
return
}
for (const n in count) {
if (count[n] > 0) {
perm.push(n)
count[n] -= 1
dfs()
count[n] += 1
perm.pop()
}
}
}
dfs()
return res
};
/**
* @param {string} s
* @return {string[][]}
*/
var partition = function (s) {
const str = s
s = s.split('')
let res = [],
part = []
var isPali = function (l, r) {
while (l < r) {
if (s[l] != s[r]) {
return false
}
l += 1
r -= 1
}
return true
}
function dfs(i) {
if (i >= s.length) {
res.push(part.slice())
return
}
for (let j = i; j < s.length; j++) {
if (isPali(i, j)) {
part.push(str.substring(i, j + 1))
dfs(j + 1)
part.pop()
}
}
}
dfs(0)
return res
};
// 79
/**
* @param {character[][]} board
* @param {string} word
* @return {boolean}
*/
var exist = function (board, word) {
const rows = board.length,
cols = board[0].length,
path = new Set()
function dfs(r, c, i) {
if (i == word.length) {
return true
}
if (r < 0 || c < 0 || r >= rows || c >= cols || word[i] != board[r][c] || path.has(r + '' + c)) {
return false
}
path.add(r + '' + c)
const res = dfs(r + 1, c, i + 1) || dfs(r - 1, c, i + 1) || dfs(r, c + 1, i + 1) || dfs(r, c - 1, i + 1)
path.delete(r + '' + c)
return res
}
for (let i = 0; i < rows; i++) {
for (let j = 0; j < cols; j++) {
if (dfs(i, j, 0)) {
return true
}
}
}
return false
};
// #93
/**
* @param {string}