7.暴力递归

91 阅读1分钟
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

image.png