我们一起搞回溯(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} s
 * @return {string[]}
 */
var restoreIpAddresses = function (s) {
    let res = []
    if (s.length > 12) {
        return res
    }

    var backtrack = function (i, dots, curIP) {
        if (dots == 4 && i == s.length) {
            res.push(curIP.substring(0, curIP.length - 1))
            return
        }
        if (dots > 4) {
            return
        }

        let range = Math.min(i + 3, s.length)

        for (let j = i; j < range; j++) {
            if (
                parseInt(s.substring(i, j + 1)) < 256 &&
                (j == i || s.charAt(i) != '0')
            ) {
                backtrack(j + 1, dots + 1, curIP + s.substring(i, j + 1) + '.')
            }
        }
    }

    backtrack(0, 0, '')
    return res
};

posted @ 2022-10-05 15:33  艾路  阅读(21)  评论(0编辑  收藏  举报