回溯是算法最重要的思想之一,主要解决一些暴力枚举也搞不定的问题,例如组合、分隔、子集、排列、棋盘等。回溯可以理解为递归的拓展,但是其代码结构又特别像深度遍历N叉树,因此只要知道递归,理解回溯并不难,关键在于回溯之后有个撤销的操作(由于python的性质,数字会自动执行撤销操作)。下面我将通过三道Leetcode展示回溯的思想。
1、leetcode77,给定两个整数n、k,返回1...n中所有可能的k个数的组合。
回溯=递归+局部枚举+放下前任。终止条件为len(path)==k,循环遍历数组取值,由于不能取之前取过的,故需要一个start下标进行标记,代码参考如下:
class Solution:
def combine(self, n: int, k: int) -> List[List[int]]:
if n < k or k <=0:
return []
def dfs(n, k , start, path, res):
if len(path) == k:
res.append(path[:])
return
for i in range(start, n+1):
path.append(i)
dfs(n, k, i+1, path, res)
path.pop()
path, res = [], []
dfs(n, k, 1, path, res)
return res
2、Leetcode257,给你一个二叉树的根节点root,按任意顺序,返回所有从根节点到叶子结点的路径,该题当root节点为空时,需要返回,之后path.append(str(root.val)),终止条件是该节点为叶子结点也就是左右结点为空时,保存路径,之后分别遍历左节点和右节点,遍历完需要执行撤销,代码参考如下:
class Solution:
def binaryTreePaths(self, root: Optional[TreeNode]) -> List[str]:
def backtracking(root, path, res):
if not root:
return
path.append(str(root.val))
if not root.left and not root.right:
res.append('->'.join(path[:]))
backtracking(root.left, path, res)
backtracking(root.right, path, res)
path.pop()
path, res = [], []
backtracking(root, path, res)
return res
3、Leetcode113,给你二叉树的根节点root和一个整数目标和targetSum,找出所有从根节点到叶子节点路径总和等于给定目标和的路径。该题的终止条件为targetSum ==0同时是叶子节点,其余和上题类似,代码参考如下:
class Solution:
def pathSum(self, root: Optional[TreeNode], targetSum: int) -> List[List[int]]:
def backtracking(root, path, res, targetSum):
if not root:
return
path.append(root.val)
targetSum -= root.val
if targetSum == 0 and not root.left and not root.right:
res.append(path[:])
backtracking(root.left, path, res, targetSum)
backtracking(root.right, path, res, targetSum)
path.pop()
targetSum += root.val
path, res = [], []
backtracking(root, path, res, targetSum)
return res