1.汉诺塔
function hanoi(n) {
process(n, 'L', 'R', 'M')
}
function process(i, from, to, temp) {
if (i == 1) { // base-case
console.log(`move 1 from ${from} to ${to}`)
return
}
// strategy:把1~i-1这个整体移到temp,i移入to,最后1~i-1由temp移入to
process(i - 1, from, temp, to) // 先把1到i-1放入temp
console.log(`move ${i} from ${from} to ${to}`) // 把i放入to
process(i - 1, temp, to, from) // 最后把1到i-1由temp放入to
// 相当于把1~i-1当作一个整体,i当作一个整体
}
2.求字符串的所有子串
function main(str) {
process(0, str, '')
}
function process(i, str, res) {
if (i == str.length) { // base case
console.log(res)
return
}
// strategy
process(i + 1, str, res + str[i]) // 要当前字符
process(i + 1, str, res) // 不要当前字符
}
3.求字符串的全部排列
// 解一
function main(str) {
let arr = []
process(str, arr, '')
return arr
}
function process(str, arr, res) {
if (str.length == 0) { // base-case
arr.push(res)
return
}
let set = new Set() // 分支限界,去掉重复的情况
// strategy
for (let j = 0; j < str.length; j++) {
if (set.has(str[j])) continue
set.add(str[j])
process(str.slice(0, j) + str.slice(j + 1), arr, res + str[j]) // 取第j个字符
}
}
// 解二
function main(str) {
let arr = []
process(str, 0, arr)
return arr
}
function process(str, i, arr) {
if (i == str.length) { // base-case
arr.push(str)
return
}
let set = new Set() // 分支限界,去掉重复的情况
// strategy
for (let j = i; j < str.length; j++) {
if (set.has(str[j])) continue
set.add(str[j])
process(swap(str, i, j), i + 1, arr) // 交换字符串的i和j
}
}
function swap(str, i, j) {
str = str.split('')
;[ str[i], str[j] ] = [ str[j], str[i] ]
return str.join('')
}
4.二人抓纸牌
function main(arr) {
return Math.max(f(arr, 0, arr.length - 1), s(arr, 0, arr.length - 1))
}
function f(arr, l, r) { // 先手
if (l == r) return arr[l] // base-case
// strategy
return Math.max(arr[l] + s(arr, l + 1, r), arr[r] + s(arr, l, r - 1))
}
function s(arr, l, r) { // 后手
if (l == r) return 0 // base-case
// strategy
return Math.min(f(arr, l + 1, r), f(arr, l, r - 1))
}
5.若 '1' 可替换 'A','2' 替换 'B','12' 替换 'L' ...,求由数字构成的字符串可转换成英文字符串的所有情况,例如 '111' 可转换为 'AAA','KA','AK'
function main(str) {
let arr = []
process(str, 0, arr, '')
return arr
}
function process(str, i, arr, res) {
if (i == str.length) { // base-cae
arr.push(res)
return
}
if (str[i] == '0') return // 分支界限
// strategy
if (str[i] == '1' && i + 1 < str.length) {
process(str, i + 2, arr, res + String.fromCharCode(+str.slice(i, i + 2) + 64)) // i和i+1
}
if (str[i] == '2' && i + 1 < str.length && str[i + 1] <= '6') {
process(str, i + 2, arr, res + String.fromCharCode(+str.slice(i, i + 2) + 64)) // i和i+1
}
process(str, i + 1,arr, res + String.fromCharCode(+str[i] + 64)) // 单独i
}
6.背包问题
function main(weights, values, maxWeight) {
return process(weights, values, maxWeight, 0, 0)
}
function process(weights, values, maxWeight, i, readyWeight) {
if (i == values.length) return 0 // base-case
// 分支界限
if (weights[i] + readyWeight > maxWeight) return process(weights, values, maxWeight, i + 1, readyWeight)
// strategy
return Math.max(
values[i] + process(weights, values, maxWeight, i + 1, readyWeight + weights[i]),
process(weights, values, maxWeight, i + 1, readyWeight)
)
}
7.不用额外空间的情况下,逆序一个栈
function reverseStack(stack) {
if (stack.isEmpty()) return
let i = process(stack)
reverseStack(stack)
stack.push(i)
}
function process(stack) {
let i = stack.pop()
if (stack.isEmpty()) {
return i
} else {
let last = process(stack)
stack.push(i)
return last
}
}
8.八皇后
// 1.常规解 O(n^n)
function main(n) {
let record = []
return process(0, record, n)
}
function process(i, record, n) { // 行i,列j
if (i == n) return 1 // base-case
let res = 0
// strategy
for (let j = 0; j < n; j++) { // i行所有列
if (isValid(record, i, j)) { // 分支界限
record[i] = j
res += process(i + 1, record, n)
}
}
return res
}
function isValid(record, i, j) {
// 与之前的都不共列和斜线(斜率绝对值为1)
for (let k = 0; k < i; k++) {
if (j == record[k] || Math.abs(j - record[k]) / Math.abs(i - k) == 1) return false
}
return true
}
// 2.最优解,采用位运算,时间复杂度还是O(n^n),但是常数明显小很多
function num(n) {
if (n < 1 || n > 32) return 0
let limit = n == 32 ? -1 : (1 << n) - 1 // 列位全1时即为完整放满的情况,例如4皇后时,limit = 1<<4 -1 = 01111
return process(limit, 0, 0, 0) // 开始时,三种限制都为0
}
function process(limit, colLim, leftLim, rightLim) {
// colLim 列限制 leftLim 左限制 rightLim 右限制
if (colLim == limit) return 1 // 放满返1
let pos = limit & (~(colLim | leftLim | rightLim)) // 合法列
let rightOne = 0 // 最右1
let res = 0
while (pos) {
rightOne = pos & (~pos + 1)
pos -= rightOne
res += process(
limit,
colLim | rightOne,
(leftLim | rightOne) << 1,
(rightLim | rightOne) >> 1
)
}
return res
}
二进制减法图解:前借+2,被借-1