深度优先搜索

428 阅读20分钟
  • 勇往直前的深度优先遍历:

    • dfs.png
    • 104. 二叉树的最大深度

      # Definition for a binary tree node.
      # class TreeNode:
      #     def __init__(self, val=0, left=None, right=None):
      #         self.val = val
      #         self.left = left
      #         self.right = right
      class Solution:
          def maxDepth(self, root: Optional[TreeNode]) -> int:
              # 预处理
              if not root:
                  return 0
              if not root.left and not root.right:
                  return 1
              # 正体递归
              leftRet = self.maxDepth(root.left)
              rightRet = self.maxDepth(root.right)
              # 后处理
              return 1 + max(leftRet, rightRet)
      
    • 111. 二叉树的最小深度

      # Definition for a binary tree node.
      # class TreeNode:
      #     def __init__(self, val=0, left=None, right=None):
      #         self.val = val
      #         self.left = left
      #         self.right = right
      class Solution:
          def minDepth(self, root: Optional[TreeNode]) -> int:
              # 预处理
              if not root:
                  return 0
              if not root.left and not root.right:
                  return 1
              # 正体递归
              leftRet = self.minDepth(root.left)
              rightRet = self.minDepth(root.right)
              # 后处理
              if root.left and root.right:
                  return 1 + min(leftRet, rightRet)
              elif root.left:
                  return 1 + leftRet
              else:
                  return 1 + rightRet
      
    • 112. 路径总和

      # Definition for a binary tree node.
      # class TreeNode:
      #     def __init__(self, val=0, left=None, right=None):
      #         self.val = val
      #         self.left = left
      #         self.right = right
      class Solution:
          def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:
              # 预处理
              if not root:
                  return False
              if not root.left and not root.right:
                  return root.val == targetSum
              # 正体递归
              leftRet = self.hasPathSum(root.left, targetSum-root.val)
              rightRet = self.hasPathSum(root.right, targetSum-root.val)
              # 后处理
              return leftRet or rightRet
      
    • 226. 翻转二叉树

      # Definition for a binary tree node.
      # class TreeNode:
      #     def __init__(self, val=0, left=None, right=None):
      #         self.val = val
      #         self.left = left
      #         self.right = right
      class Solution:
          def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
              # 预处理
              if not root:
                  return root
              if not root.left and not root.right:
                  return root
              # 正体递归
              leftRet = self.invertTree(root.left)
              rightRet = self.invertTree(root.right)
              # 后处理
              root.left, root.right = rightRet, leftRet
              return root
      
    • 100. 相同的树

      # Definition for a binary tree node.
      # class TreeNode:
      #     def __init__(self, val=0, left=None, right=None):
      #         self.val = val
      #         self.left = left
      #         self.right = right
      class Solution:
          def isSameTree(self, p: Optional[TreeNode], q: Optional[TreeNode]) -> bool:
              # 预处理
              if not p and not q:
                  return True
              if p and not q:
                  return False
              if not p and q:
                  return False
              if p.val != q.val:
                  return False
              # 正体递归
              leftRet = self.isSameTree(p.left, q.left)
              rightRet = self.isSameTree(p.right, q.right)
              # 后处理
              return leftRet and rightRet
      
    • 101. 对称二叉树

      # Definition for a binary tree node.
      # class TreeNode:
      #     def __init__(self, val=0, left=None, right=None):
      #         self.val = val
      #         self.left = left
      #         self.right = right
      class Solution:
          def isSymmetric(self, root: Optional[TreeNode]) -> bool:
              # 因为该问题没有重复的子问题,所以无法在本身递归,比如你知道左子树和右子树分别是对称的
              # 但不能结合起来结合父树的对称问题,所以需要一个辅助dfs
              def dfs(p: Optional[TreeNode], q: Optional[TreeNode]) -> bool:
                  # 预处理
                  if not p and not q:
                      return True
                  if not p and q:
                      return False
                  if p and not q:
                      return False
                  if p.val != q.val:
                      return False
                  # 正体递归
                  leftRet = dfs(p.left, q.right)
                  rightRet = dfs(p.right, q.left)
                  # 后处理
                  return leftRet and rightRet
      
              return dfs(root, root)
      
    • 129. 求根节点到叶节点数字之和

      # Definition for a binary tree node.
      # class TreeNode:
      #     def __init__(self, val=0, left=None, right=None):
      #         self.val = val
      #         self.left = left
      #         self.right = right
      class Solution:
          def sumNumbers(self, root: Optional[TreeNode]) -> int:
              def dfs(node: Optional[TreeNode], path: List[str]) -> int:
                  # 预处理
                  if not node:
                      return 0
                  if not node.left and not node.right:
                      path.append(str(node.val))
                      ans = int("".join(path))
                      path.pop()
                      return ans
                  # 正体递归
                  path.append(str(node.val))
                  leftRet = dfs(node.left, path)
                  path.pop()
                  path.append(str(node.val))
                  rightRet = dfs(node.right, path)
                  path.pop()
                  # 后处理
                  return leftRet + rightRet
      
              return dfs(root, [])
      
    • 236. 二叉树的最近公共祖先

      # Definition for a binary tree node.
      # class TreeNode:
      #     def __init__(self, x):
      #         self.val = x
      #         self.left = None
      #         self.right = None
      class Solution:
          def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
              # 预处理
              if not root:
                  return root
              if root.val in [p.val, q.val]:
                  return root
              if not root.left and not root.right:
                  return None
              # 正体递归
              leftRet = self.lowestCommonAncestor(root.left, p, q)
              rightRet = self.lowestCommonAncestor(root.right, p, q)
              # 后处理
              if leftRet and rightRet:
                  return root
              elif leftRet:
                  return leftRet
              else:
                  return rightRet
      
    • 105. 从前序与中序遍历序列构造二叉树

      # Definition for a binary tree node.
      # class TreeNode:
      #     def __init__(self, val=0, left=None, right=None):
      #         self.val = val
      #         self.left = left
      #         self.right = right
      class Solution:
          def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
              # 预处理
              if not preorder or not inorder:
                  return None
              # 正体递归
              rootVal = preorder[0]
              index = inorder.index(rootVal)
              leftRet = self.buildTree(preorder[1:index+1], inorder[:index])
              rightRet = self.buildTree(preorder[index+1:], inorder[index+1:])
              # 后处理
              return TreeNode(rootVal, leftRet, rightRet)
      
    • 106. 从中序与后序遍历序列构造二叉树

      # Definition for a binary tree node.
      # class TreeNode:
      #     def __init__(self, val=0, left=None, right=None):
      #         self.val = val
      #         self.left = left
      #         self.right = right
      class Solution:
          def buildTree(self, inorder: List[int], postorder: List[int]) -> Optional[TreeNode]:
              # 预处理
              if not inorder or not postorder:
                  return None
              # 正体递归
              rootVal = postorder[-1]
              index = inorder.index(rootVal)
              leftRet = self.buildTree(inorder[:index], postorder[:index])
              rightRet = self.buildTree(inorder[index+1:], postorder[index:-1])
              # 后处理
              return TreeNode(rootVal, leftRet, rightRet)
      
    • 1008. 前序遍历构造二叉搜索树

      # Definition for a binary tree node.
      # class TreeNode:
      #     def __init__(self, val=0, left=None, right=None):
      #         self.val = val
      #         self.left = left
      #         self.right = right
      class Solution:
          def bstFromPreorder(self, preorder: List[int]) -> Optional[TreeNode]:
              # 预处理
              if not preorder:
                  return None
              # 正体递归
              rootVal = preorder[0]
              idx = 0
              for i in range(1, len(preorder)):
                  if preorder[i] < rootVal:
                      idx = i
              leftRet = self.bstFromPreorder(preorder[1:idx+1])
              rightRet = self.bstFromPreorder(preorder[idx+1:])
              # 后处理
              return TreeNode(rootVal, leftRet, rightRet)
      
    • 1028. 从先序遍历还原二叉树

      # Definition for a binary tree node.
      # class TreeNode:
      #     def __init__(self, val=0, left=None, right=None):
      #         self.val = val
      #         self.left = left
      #         self.right = right
      class Solution:
          def recoverFromPreorder(self, traversal: str) -> Optional[TreeNode]:
              idx = 0
              def dfs(depth: int) -> Optional[TreeNode]:
                  # 预处理
                  nonlocal idx
                  hyphenCnt = 0
                  while idx+hyphenCnt < len(traversal) and traversal[idx+hyphenCnt] == "-":
                      hyphenCnt += 1
                  if depth != hyphenCnt: return None
                  # 正体递归
                  intEnd = idx + hyphenCnt
                  while intEnd < len(traversal) and traversal[intEnd].isdigit():
                      intEnd += 1
                  rootVal = int(traversal[idx+hyphenCnt:intEnd])
                  idx = intEnd
                  leftRet = dfs(depth + 1)
                  rightRet = dfs(depth + 1)
                  # 后处理
                  return TreeNode(rootVal, leftRet, rightRet)
      
              return dfs(0)
      
  • 数据结构 - 栈

    • stack.png
    • 144. 二叉树的前序遍历

      # Definition for a binary tree node.
      # class TreeNode:
      #     def __init__(self, val=0, left=None, right=None):
      #         self.val = val
      #         self.left = left
      #         self.right = right
      class Solution:
          def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
              # 预处理
              if not root:
                  return []
              if not root.left and not root.right:
                  return [root.val]
              # 正体递归
              leftRet = self.preorderTraversal(root.left)
              rightRet = self.preorderTraversal(root.right)
              # 后处理
              return [root.val] + leftRet + rightRet
      
    • 94. 二叉树的中序遍历

      # Definition for a binary tree node.
      # class TreeNode:
      #     def __init__(self, val=0, left=None, right=None):
      #         self.val = val
      #         self.left = left
      #         self.right = right
      class Solution:
          def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
              # 预处理
              if not root:
                  return []
              if not root.left and not root.right:
                  return [root.val]
              # 正体递归
              leftRet = self.inorderTraversal(root.left)
              rightRet = self.inorderTraversal(root.right)
              # 后处理
              return leftRet + [root.val] + rightRet
      
    • 145. 二叉树的后序遍历

      # Definition for a binary tree node.
      # class TreeNode:
      #     def __init__(self, val=0, left=None, right=None):
      #         self.val = val
      #         self.left = left
      #         self.right = right
      class Solution:
          def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
              # 预处理
              if not root:
                  return []
              if not root.left and not root.right:
                  return [root.val]
              # 正体递归
              leftRet = self.postorderTraversal(root.left)
              rightRet = self.postorderTraversal(root.right)
              # 后处理
              return leftRet + rightRet + [root.val]
      
    • 589. N 叉树的前序遍历

      """
      # Definition for a Node.
      class Node:
          def __init__(self, val=None, children=None):
              self.val = val
              self.children = children
      """
      class Solution:
          def preorder(self, root: 'Node') -> List[int]:
              # 预处理
              if not root:
                  return []
              if not root.children:
                  return [root.val]
              # 正体递归
              ret = []
              for child in root.children:
                  ret += self.preorder(child)
              # 后处理
              return [root.val] + ret
      
    • 590. N 叉树的后序遍历

      """
      # Definition for a Node.
      class Node:
          def __init__(self, val=None, children=None):
              self.val = val
              self.children = children
      """
      class Solution:
          def postorder(self, root: 'Node') -> List[int]:
              # 预处理
              if not root:
                  return []
              if not root.children:
                  return [root.val]
              # 正体递归
              ret = []
              for child in root.children:
                  ret += self.postorder(child)
              # 后处理
              return ret + [root.val]
      
  • 深度优先遍历的应用

    • application.png
    • 129. 求根节点到叶节点数字之和

      # Definition for a binary tree node.
      # class TreeNode:
      #     def __init__(self, val=0, left=None, right=None):
      #         self.val = val
      #         self.left = left
      #         self.right = right
      class Solution:
          def sumNumbers(self, root: Optional[TreeNode]) -> int:
              def dfs(node: Optional[TreeNode], path: List[int]) -> int:
                  # 预处理
                  if not node:
                      return 0
                  path.append(node.val)
                  if not node.left and not node.right:
                      ret = int("".join(map(str, path)))
                      path.pop()
                      return ret
                  # 正体递归
                  leftRet = dfs(node.left, path)
                  rightRet = dfs(node.right, path)
                  path.pop()
                  # 后处理
                  return leftRet + rightRet
      
              return dfs(root, [])
      
    • 323. 无向图中连通分量的数目

      class Solution:
          def countComponents(self, n: int, edges: List[List[int]]) -> int:
              graph = defaultdict(list)
              for u, v in edges:
                  graph[u].append(v)
                  graph[v].append(u)
              vis = set()
              # 这里的辅助dfs只是为了在图中遍历标记路经用,所以无需返回什么
              def dfs(node: int):
                  vis.add(node)
                  for nei in graph[node]:
                      if nei not in vis:
                          dfs(nei)
      
              ans = 0
              for i in range(n):
                  if i not in vis:
                      dfs(i)
                      ans += 1
              return ans
      
    • 684. 冗余连接

      class Solution:
          def findRedundantConnection(self, edges: List[List[int]]) -> List[int]:
              def dfs(node: int):
                  vis.add(node)
                  for nei in graph[node]:
                      if nei not in vis:
                          dfs(nei)
      
              graph = defaultdict(list)
              for u, v in edges:
                  if u not in graph or v not in graph:
                      graph[u].append(v)
                      graph[v].append(u)
                  else:
                      vis = set()
                      dfs(u)
                      if v in vis:
                          return [u, v]
                      graph[u].append(v)
                      graph[v].append(u)
      
    • 802. 找到最终的安全状态

      class Solution:
          def eventualSafeNodes(self, graph: List[List[int]]) -> List[int]:
              # 三色问题,再次遇到GRAY时即说明有环,color数组也充当vis角色
              # WHITE:未遍历过;GRAY:进入时染色;BLACK:退出时染色
              WHITE, GRAY, BLACK = 0, 1, 2
              n = len(graph)
              color = [WHITE] * n
              # dfs返回该节点是否安全
              def dfs(node: int) -> bool:
                  if color[node] != WHITE:
                      return color[node] == BLACK
                  color[node] = GRAY
                  for nei in graph[node]:
                      if not dfs(nei):
                          return False
                  color[node] = BLACK
                  return True
      
              return [i for i in range(n) if dfs(i)]
      
    • 785. 判断二分图

      class Solution:
          def isBipartite(self, graph: List[List[int]]) -> bool:
              # 三色问题,如果两相邻节点染的颜色不同,即无法二分,color数组也充当vis角色
              # WHITE:未遍历过;GRAY:颜色一;BLACK:颜色二
              WHITE, GRAY, BLACK = 0, 1, 2
              n = len(graph)
              color = [WHITE] * n
              # dfs返回该节点在二分图条件下是否可以被顺利染色
              def dfs(node: int, c: int) -> bool:
                  if color[node] != WHITE:
                      return color[node] == c
                  color[node] = c
                  cNei = (BLACK if c == GRAY else GRAY)
                  for nei in graph[node]:
                      if not dfs(nei, cNei):
                          return False
                  return True
      
              for i in range(n):
                  if color[i] == WHITE:
                      if not dfs(i, GRAY):
                          return False
              return True
      
    • 210. 课程表 II

      class Solution:
          def findOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]:
              graph = defaultdict(list)
              for u, v in prerequisites:
                  graph[v].append(u)
              # 三色问题,再次遇到GRAY时即说明有环,color数组也充当vis角色
              # WHITE:未遍历过;GRAY:进入时染色;BLACK:退出时染色
              WHITE, GRAY, BLACK = 0, 1, 2
              color = [WHITE] * numCourses
              ans = []
              # dfs返回该节点进入后是否无环,即无法完成全部课程的情况
              # ans中依次记录dfs退出时的节点顺序,如果无环,那么退出时的顺序即学习课程的逆序
              def dfs(node: int) -> bool:
                  if color[node] != WHITE:
                      return color[node] == BLACK
                  color[node] = GRAY
                  for nei in graph[node]:
                      if not dfs(nei):
                          return False
                  color[node] = BLACK
                  ans.append(node)
                  return True
      
              for i in range(numCourses):
                  if color[i] == WHITE:
                      if not dfs(i):
                          return []
              return ans[::-1]
      
    • 207. 课程表

      class Solution:
          def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
              graph = defaultdict(list)
              for u, v in prerequisites:
                  graph[v].append(u)
              # 三色问题,再次遇到GRAY时即说明有环,color数组也充当vis角色
              # WHITE:未遍历过;GRAY:进入时染色;BLACK:退出时染色
              WHITE, GRAY, BLACK = 0, 1, 2
              color = [WHITE] * numCourses
              # dfs返回该节点进入后是否无环,即无法完成全部课程的情况
              def dfs(node: int) -> bool:
                  if color[node] != WHITE:
                      return color[node] == BLACK
                  color[node] = GRAY
                  for nei in graph[node]:
                      if not dfs(nei):
                          return False
                  color[node] = BLACK
                  return True
      
              for i in range(numCourses):
                  if color[i] == WHITE:
                      if not dfs(i):
                          return False
              return True
      
    • 1136. 并行课程

      class Solution:
          def minimumSemesters(self, n: int, relations: List[List[int]]) -> int:
              graph = defaultdict(list)
              for u, v in relations:
                  graph[u].append(v)
              # 三色问题,再次遇到GRAY时即说明有环,color数组也充当vis角色
              # WHITE:未遍历过;GRAY:进入时染色;BLACK:退出时染色
              WHITE, GRAY, BLACK = 0, 1, 2
              color = [WHITE] * (n+1)
              # dfs返回该节点进入后是否无环,即无法完成全部课程的情况
              def dfs(node: int) -> bool:
                  if color[node] != WHITE:
                      return color[node] == BLACK
                  color[node] = GRAY
                  for nei in graph[node]:
                      if not dfs(nei):
                          return False
                  color[node] = BLACK
                  return True
              # 如果无法完成全部课程,提前退出
              for i in range(1, n+1):
                  if color[i] == WHITE:
                      if not dfs(i):
                          return -1
      
              node2depth = {}
              # 求出node节点出发的众多路径的最大深度,即修完该节点出发所需的最大学期数
              def dfs1(node: int) -> int:
                  if node in node2depth:
                      return node2depth[node]
                  ret = 0
                  for nei in graph[node]:
                      depth = dfs1(nei)
                      ret = max(ret, depth)
                  node2depth[node] = ret + 1
                  return node2depth[node]
      
              # 完全部课程所需的最少学期数即为;最长的一条dfs路径的长度
              ans = 0
              for i in range(1, n+1):
                  if i not in node2depth:
                      depth = dfs1(i)
                      ans = max(ans, depth)
              return ans
      
    • 886. 可能的二分法

      class Solution:
          def possibleBipartition(self, n: int, dislikes: List[List[int]]) -> bool:
              graph = defaultdict(list)
              for u, v in dislikes:
                  graph[u].append(v)
                  graph[v].append(u)
              # 三色问题,再次遇到GRAY时即说明有环,color数组也充当vis角色
              # WHITE:未遍历过;GRAY:进入时染色;BLACK:退出时染色
              WHITE, GRAY, BLACK = 0, 1, 2
              color = [WHITE] * (n+1)
              # dfs返回该节点在二分图条件下是否可以被顺利染色
              def dfs(node: int, c: int) -> bool:
                  if color[node] != WHITE:
                      return color[node] == c
                  color[node] = c
                  cNei = (BLACK if c == GRAY else GRAY)
                  for nei in graph[node]:
                      if not dfs(nei, cNei):
                          return False
                  return True
      
              for i in range(1, n+1):
                  if color[i] == WHITE:
                      if not dfs(i, GRAY):
                          return False
              return True
      
  • 回溯算法

    • backtrack.png
    • 51. N 皇后

      class Solution:
          def solveNQueens(self, n: int) -> List[List[str]]:
              ans = []
              cols, slash, backslash = set(), set(), set()
              def dfs(row: int, path: List[int]):
                  if row >= n:
                      ans.append(path[:])
                      return
                  for col in range(n):
                      if col in cols or row+col in slash or row-col in backslash:
                          continue
                      cols.add(col), slash.add(row+col), backslash.add(row-col)
                      path.append(col)
                      dfs(row + 1, path)
                      path.pop()
                      cols.remove(col), slash.remove(row+col), backslash.remove(row-col)
      
              dfs(0, [])
              def paint():
                  board = []
                  for pattern in ans:
                      for col in pattern:
                          board.append('.'*col + 'Q' + '.'*(n-col-1))
                  return [board[i:i+n] for i in range(0, len(board), n)]
      
              return paint()
      
    • 46. 全排列

      class Solution:
          def permute(self, nums: List[int]) -> List[List[int]]:
              n = len(nums)
              vis = [False] * n
              ans = []
              def dfs(idx: int, path: List[int]):
                  if idx >= n:
                      ans.append(path[:])
                      return
                  for i in range(n):
                      if vis[i]:
                          continue
                      vis[i] = True
                      path.append(nums[i])
                      dfs(idx + 1, path)
                      path.pop()
                      vis[i] = False
      
              dfs(0, [])
              return ans
      
    • 37. 解数独

      class Solution:
          def solveSudoku(self, board: List[List[str]]) -> None:
              def valid(r: int, c: int, ch: str) -> bool:
                  for i in range(len(board)):
                      if board[r][i] == ch:
                          return False
                      if board[i][c] == ch:
                          return False
                      if board[3*(r//3) + i//3][3*(c//3) + i%3] == ch:
                          return False
                  return True
      
              def dfs() -> bool:
                  for i in range(len(board)):
                      for j in range(len(board[0])):
                          if board[i][j] != ".":
                              continue
                          for ch in "123456789":
                              if not valid(i, j, ch):
                                  continue
                              board[i][j] = ch
                              if dfs():
                                  return True
                              board[i][j] = "."
                          return False
                  return True
      
              dfs()
      
    • 22. 括号生成

      class Solution:
          def generateParenthesis(self, n: int) -> List[str]:
              ans = []
              left, right = 0, 0
              def dfs(idx: int, path: List[str]):
                  nonlocal left, right
                  if idx >= 2 * n:
                      if left == n and right == n:
                          ans.append("".join(path))
                      return
                  # 广义剪枝
                  if left > n or right > n or right > left:
                      return
                  path.append("(")
                  left += 1
                  dfs(idx + 1, path)
                  left -= 1
                  path.pop()
                  path.append(")")
                  right += 1
                  dfs(idx + 1, path)
                  right -= 1
                  path.pop()
      
              dfs(0, [])
              return ans
      
    • 17. 电话号码的字母组合

      class Solution:
          def letterCombinations(self, digits: str) -> List[str]:
              if not digits:
                  return []
              keyMap = {
                  "2": "abc", "3": "def", "4": "ghi",
                  "5": "jkl", "6": "mno", "7": "pqrs",
                  "8": "tuv", "9": "wxyz",
              }
              ans = []
              def dfs(idx: int, path: List[str]):
                  if idx >= len(digits):
                      ans.append("".join(path))
                      return
                  digit = digits[idx]
                  for ch in keyMap[digit]:
                      path.append(ch)
                      dfs(idx + 1, path)
                      path.pop()
      
              dfs(0, [])
              return ans
      
    • 784. 字母大小写全排列

      class Solution:
          def letterCasePermutation(self, s: str) -> List[str]:
              n = len(s)
              ans = []
              def dfs(idx: int, path: List[str]):
                  if idx >= n:
                      ans.append("".join(path))
                      return
                  if s[idx].isdigit():
                      path.append(s[idx])
                      dfs(idx + 1, path)
                      path.pop()
                      return
                  path.append(s[idx].lower())
                  dfs(idx + 1, path)
                  path.pop()
                  path.append(s[idx].upper())
                  dfs(idx + 1, path)
                  path.pop()
      
              dfs(0, [])
              return ans
      
  • 剪枝

    • pruning.png
    • 47. 全排列 II

      class Solution:
          def permuteUnique(self, nums: List[int]) -> List[List[int]]:
              nums.sort()
              n = len(nums)
              vis = [False] * n
              ans = []
              def dfs(idx: int, path: List[int]):
                  if idx >= n:
                      ans.append(path[:])
                      return
                  for i in range(n):
                      if vis[i]:
                          continue
                      # 重复性剪枝
                      if i > 0 and nums[i] == nums[i-1] and not vis[i-1]:
                          continue
                      vis[i] = True
                      path.append(nums[i])
                      dfs(idx + 1, path)
                      path.pop()
                      vis[i] = False
      
              dfs(0, [])
              return ans
      
    • 39. 组合总和

      class Solution:
          def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
              n = len(candidates)
              ans = []
              tmpSum = 0
              def dfs(idx: int, path: List[int]):
                  nonlocal tmpSum
                  if idx >= n:
                      if tmpSum == target:
                          ans.append(path[:])
                      return
                  # 广义剪枝
                  if tmpSum > target:
                      return
                  dfs(idx + 1, path)
                  tmpSum += candidates[idx]
                  path.append(candidates[idx])
                  dfs(idx, path)
                  path.pop()
                  tmpSum -= candidates[idx]
      
              dfs(0, [])
              return ans
      
    • 77. 组合

      class Solution:
          def combine(self, n: int, k: int) -> List[List[int]]:
              ans = []
              def dfs(idx: int, path: List[int]):
                  if idx > n:
                      if len(path) == k:
                          ans.append(path[:])
                      return
                  # 广义剪枝
                  if len(path) > k:
                      return
                  dfs(idx + 1, path)
                  path.append(idx)
                  dfs(idx + 1, path)
                  path.pop()
      
              dfs(1, [])
              return ans
      
    • 473. 火柴拼正方形

      class Solution:
          def makesquare(self, matchsticks: List[int]) -> bool:
              n = len(matchsticks)
              perimeter = sum(matchsticks)
              if perimeter % 4:
                  return False
              possible_side = perimeter // 4
              # 为了减少搜索量,需要对火柴长度从大到小进行排序
              matchsticks.sort(reverse=True)
              edges = [0] * 4
              def dfs(idx: int) -> bool:
                  if idx >= n:
                      return edges[0] == edges[1] == edges[2] == possible_side
                  for i in range(4):
                      if edges[i] + matchsticks[idx] <= possible_side:
                          edges[i] += matchsticks[idx]
                          if dfs(idx + 1):
                              return True
                          edges[i] -= matchsticks[idx]
                  return False
              return dfs(0)
      
    • 40. 组合总和 II

      class Solution:
          def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
              candidates.sort()
              n = len(candidates)
              vis = [False] * n
              ans = []
              tmpSum = 0
              def dfs(idx: int, path: List[int]):
                  nonlocal tmpSum
                  if idx >= n:
                      if tmpSum == target:
                          ans.append(path[:])
                      return
                  # 广义剪枝
                  if tmpSum > target:
                      return
                  # 重复性剪枝
                  if idx > 0 and candidates[idx] == candidates[idx-1] and not vis[idx-1]:
                      dfs(idx + 1, path)
                      return
                  dfs(idx + 1, path)
                  vis[idx] = True
                  tmpSum += candidates[idx]
                  path.append(candidates[idx])
                  dfs(idx + 1, path)
                  path.pop()
                  tmpSum -= candidates[idx]
                  vis[idx] = False
      
              dfs(0, [])
              return ans
      
    • 78. 子集

      class Solution:
          def subsets(self, nums: List[int]) -> List[List[int]]:
              n = len(nums)
              ans = []
              def dfs(idx: int, path: List[int]):
                  if idx >= n:
                      ans.append(path[:])
                      return
                  dfs(idx + 1, path)
                  path.append(nums[idx])
                  dfs(idx + 1, path)
                  path.pop()
      
              dfs(0, [])
              return ans
      
    • 90. 子集 II

      class Solution:
          def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
              n = len(nums)
              vis = [False] * n
              ans = []
              nums.sort()
              def dfs(idx: int, path: List[int]):
                  if idx >= n:
                      ans.append(path[:])
                      return
                  # 重复性剪枝
                  if idx > 0 and nums[idx] == nums[idx-1] and not vis[idx-1]:
                      dfs(idx + 1, path)
                      return
                  dfs(idx + 1, path)
                  vis[idx] = True
                  path.append(nums[idx])
                  dfs(idx + 1, path)
                  path.pop()
                  vis[idx] = False
      
              dfs(0, [])
              return ans
      
    • 1593. 拆分字符串使唯一子字符串的数目最大

      class Solution:
          def maxUniqueSplit(self, s: str) -> int:
              n = len(s)
              seen = set()
              ans = []
              def dfs(idx: int, path: List[str]):
                  if idx >= n:
                      ans.append(path[:])
                      return
                  for i in range(1, (n-1 - idx + 1) + 1):
                      substr = s[idx:idx+i]
                      if substr in seen:
                          continue
                      seen.add(substr)
                      path.append(substr)
                      dfs(idx + i, path)
                      path.pop()
                      seen.remove(substr)
      
              dfs(0, [])
              return max(map(len, ans))
      
    • 1079. 活字印刷

      class Solution:
          def numTilePossibilities(self, tiles: str) -> int:
              n = len(tiles)
              tiles = sorted(tiles)
              vis = [False] * n
              ans = []
              def dfs(path: List[str]):
                  ans.append("".join(path))
                  for i in range(n):
                      if vis[i]:
                          continue
                      # 重复性剪枝
                      if i > 0 and tiles[i] == tiles[i-1] and not vis[i-1]:
                          continue
                      vis[i] = True
                      path.append(tiles[i])
                      dfs(path)
                      path.pop()
                      vis[i] = False
      
              dfs([])
              return len(ans)-1
      
  • 二维平面上的搜索问题(Flood Fill)

    • 79. 单词搜索

      class Solution:
          def exist(self, board: List[List[str]], word: str) -> bool:
              m, n, wordLen = len(board), len(board[0]), len(word)
              vis = set()
              dr = [-1,1,0,0]
              dc = [0,0,-1,1]
              def dfs(idx: int, r: int, c: int) -> bool:
                  if idx >= wordLen:
                      return True
                  # 广义剪枝
                  if not (0 <= r < m) or not (0 <= c < n):
                      return False
                  if (r, c) in vis:
                      return False
                  if word[idx] != board[r][c]:
                      return False
                  vis.add((r, c))
                  for i in range(4):
                      nr, nc = r + dr[i], c + dc[i]
                      if dfs(idx + 1, nr, nc):
                          return True
                  vis.remove((r,c))
                  return False
      
              for i in range(m):
                  for j in range(n):
                      if dfs(0, i, j):
                          return True
              return False
      
    • 695. 岛屿的最大面积

      class Solution:
          def maxAreaOfIsland(self, grid: List[List[int]]) -> int:
              m, n = len(grid), len(grid[0])
              vis = set()
              dr = [-1,1,0,0]
              dc = [0,0,-1,1]
              def dfs(r: int, c: int) -> int:
                  # 广义剪枝
                  if not (0 <= r < m) or not (0 <= c < n):
                      return 0
                  if grid[r][c] != 1:
                      return 0
                  if (r, c) in vis:
                      return 0
                  vis.add((r, c))
                  area = 1
                  for i in range(4):
                      nr, nc = r + dr[i], c + dc[i]
                      area += dfs(nr, nc)
                  return area
      
              ans = 0
              for i in range(m):
                  for j in range(n):
                      if grid[i][j] == 1 and (i,j) not in vis:
                          area = dfs(i, j)
                          ans = max(ans, area)
              return ans
      
    • 130. 被围绕的区域

      class Solution:
          def solve(self, board: List[List[str]]) -> None:
              m, n = len(board), len(board[0])
              dr = [-1,1,0,0]
              dc = [0,0,-1,1]
              def dfs(r: int, c: int):
                  # 广义剪枝
                  if not (0 <= r < m) or not (0 <= c < n):
                      return
                  if board[r][c] != 'O':
                      return
                  board[r][c] = "A"
                  for i in range(4):
                      nr, nc = r + dr[i], c + dc[i]
                      dfs(nr, nc)
      
              for i in range(m):
                  dfs(i, 0)
                  dfs(i, n-1)
      
              for j in range(1, n-1):
                  dfs(0, j)
                  dfs(m-1, j)
      
              for i in range(m):
                  for j in range(n):
                      if board[i][j] == "A":
                          board[i][j] = "O"
                      elif board[i][j] == "O":
                          board[i][j] = "X"
      
    • 200. 岛屿数量

      class Solution:
          def numIslands(self, grid: List[List[str]]) -> int:
              m, n = len(grid), len(grid[0])
              vis = set()
              dr = [-1,1,0,0]
              dc = [0,0,-1,1]
              def dfs(r: int, c: int):
                  # 广义剪枝
                  if not (0 <= r < m) or not (0 <= c < n):
                      return
                  if grid[r][c] == "0":
                      return
                  if (r, c) in vis:
                      return
                  vis.add((r, c))
                  for i in range(4):
                      nr, nc = r + dr[i], c + dc[i]
                      dfs(nr, nc)
      
              ans = 0
              for i in range(m):
                  for j in range(n):
                      if grid[i][j] == "1" and (i, j) not in vis:
                          dfs(i, j)
                          ans += 1
              return ans
      
    • 417. 太平洋大西洋水流问题

      class Solution:
          def pacificAtlantic(self, heights: List[List[int]]) -> List[List[int]]:
              m, n = len(heights), len(heights[0])
              dr = [0,0,-1,1]
              dc = [-1,1,0,0]
              def dfs(r: int, c: int, arr: List[List[int]]):
                  # 广义剪枝
                  if not (0 <= r < m) or not (0 <= c < n):
                      return
                  if arr[r][c] == 1:
                      return
                  arr[r][c] = 1
                  for i in range(4):
                      nr, nc = r + dr[i], c + dc[i]
                      if 0 <= nr < m and 0 <= nc < n and heights[nr][nc] >= heights[r][c]:
                          dfs(nr, nc, arr)
      
              pacific = [[0] * n for _ in range(m)]
              atlantic = [[0] * n for _ in range(m)]
              for j in range(n):
                  dfs(0, j, pacific)
                  dfs(m-1, j, atlantic)
              for i in range(m):
                  dfs(i, 0, pacific)
                  dfs(i, n-1, atlantic)
              return [[i,j] for i in range(m) for j in range(n) if pacific[i][j] and atlantic[i][j]]
      
    • 1020. 飞地的数量

      class Solution:
          def numEnclaves(self, grid: List[List[int]]) -> int:
              m, n = len(grid), len(grid[0])
              dr = [-1,1,0,0]
              dc = [0,0,-1,1]
              def dfs(r: int, c: int):
                  # 广义剪枝
                  if not (0 <= r < m) or not (0 <= c < n):
                      return
                  if grid[r][c] != 1:
                      return
                  grid[r][c] = 0
                  for i in range(4):
                      nr, nc = r + dr[i], c + dc[i]
                      dfs(nr, nc)
      
              for i in range(m):
                  dfs(i, 0)
                  dfs(i, n-1)
              for j in range(1, n-1):
                  dfs(0, j)
                  dfs(m-1, j)
      
              ans = 0
              for i in range(m):
                  for j in range(n):
                      if grid[i][j] == 1:
                          ans += 1
              return ans
      
    • 1254. 统计封闭岛屿的数目

      class Solution:
          def closedIsland(self, grid: List[List[int]]) -> int:
              m, n = len(grid), len(grid[0])
              dr = [-1,1,0,0]
              dc = [0,0,-1,1]
              def dfs(r: int, c: int):
                  # 广义剪枝
                  if not (0 <= r < m) or not (0 <= c < n):
                      return
                  if grid[r][c] != 0:
                      return
                  grid[r][c] = "x"
                  for i in range(4):
                      nr, nc = r + dr[i], c + dc[i]
                      dfs(nr, nc)
      
              for i in range(m):
                  dfs(i, 0)
                  dfs(i, n-1)
              for j in range(1, n-1):
                  dfs(0, j)
                  dfs(m-1, j)
      
              ans = 0
              for i in range(m):
                  for j in range(n):
                      if grid[i][j] == 0:
                          dfs(i, j)
                          ans += 1
              return ans
      
    • 1034. 边界着色

      class Solution:
          def colorBorder(self, grid: List[List[int]], row: int, col: int, color: int) -> List[List[int]]:
              m, n = len(grid), len(grid[0])
              vis = set()
              dr = [-1,1,0,0]
              dc = [0,0,-1,1]
              def isBorder(r: int, c: int, originalColor: int) -> bool:
                  if r in [0, m-1] or c in [0, n-1]:
                      return True
                  for i in range(4):
                      nr, nc = r + dr[i], c + dc[i]
                      if (nr, nc) not in vis and grid[nr][nc] != originalColor:
                          return True
                  return False
      
              def dfs(r: int, c: int, originalColor: int):
                  # 广义剪枝
                  if not (0 <= r < m) or not (0 <= c < n):
                      return
                  if (r, c) in vis:
                      return
                  if grid[r][c] != originalColor:
                      return
                  vis.add((r, c))
                  if isBorder(r, c, originalColor):
                      grid[r][c] = color
                  for i in range(4):
                      nr, nc = r + dr[i], c + dc[i]
                      dfs(nr, nc, originalColor)
      
              dfs(row, col, grid[row][col])
              return grid
      
    • 133. 克隆图

      """
      # Definition for a Node.
      class Node:
          def __init__(self, val = 0, neighbors = None):
              self.val = val
              self.neighbors = neighbors if neighbors is not None else []
      """
      class Solution:
          def __init__(self):
              self.vis = {}
      
          def cloneGraph(self, node: 'Node') -> 'Node':
              if not node:
                  return node
              if node in self.vis:
                  return self.vis[node]
              clone_node = Node(node.val, [])
              self.vis[node] = clone_node
              if node.neighbors:
                  clone_node.neighbors = [self.cloneGraph(n) for n in node.neighbors]
              return clone_node
      
    • 面试题13. 机器人的运动范围

      class Solution:
          def movingCount(self, m: int, n: int, k: int) -> int:
              board = [[0] * n for _ in range(m)]
              for i in range(m):
                  for j in range(n):
                      total = 0
                      for ch in str(i):
                          total += int(ch)
                      for ch in str(j):
                          total += int(ch)
                      if total <= k:
                          board[i][j] = 1
      
              vis = set()
              dr = [-1,1,0,0]
              dc = [0,0,-1,1]
              def dfs(r: int, c: int) -> int:
                  # 广义剪枝
                  if not (0 <= r < m) or not (0 <= c < n):
                      return 0
                  if board[r][c] == 0:
                      return 0
                  if (r, c) in vis:
                      return 0
                  vis.add((r, c))
                  count = 1
                  for i in range(4):
                      nr, nc = r + dr[i], c + dc[i]
                      count += dfs(nr, nc)
                  return count
      
              return dfs(0, 0)
      
    • 529. 扫雷游戏

      class Solution:
          def updateBoard(self, board: List[List[str]], click: List[int]) -> List[List[str]]:
              i, j = click[0], click[1]
              if board[i][j] == 'M':
                  board[i][j] = 'X'
                  return board
      
              m, n = len(board), len(board[0])
              dr = [-1,-1,-1,0,0,1,1,1]
              dc = [-1,0,1,-1,1,-1,0,1]
              def dfs(r: int, c: int):
                  # 广义剪枝
                  if not (0 <= r < m) or not (0 <= c < n):
                      return
                  if board[r][c] != 'E':
                      return
                  mineCnt = 0
                  for i in range(8):
                      nr, nc = r + dr[i], c + dc[i]
                      if 0 <= nr < m and 0 <= nc < n and board[nr][nc] == 'M':        
                          mineCnt += 1
      
                  if mineCnt > 0:
                      board[r][c] = str(mineCnt)
                      return
      
                  board[r][c] = 'B'
                  for i in range(8):
                      nr, nc = r + dr[i], c + dc[i]
                      dfs(nr, nc)
      
              dfs(i, j)
              return board
      
  • 动态规划与深度优先遍历思想的结合

    • 543. 二叉树的直径

      # Definition for a binary tree node.
      # class TreeNode:
      #     def __init__(self, val=0, left=None, right=None):
      #         self.val = val
      #         self.left = left
      #         self.right = right
      class Solution:
          def diameterOfBinaryTree(self, root: Optional[TreeNode]) -> int:
              ans = 0
              # dfs返回必定经过node且node为端点的最长路径长度
              # 利用「无后效性」的思想(固定住一些状态,或者对当前维度进行升维)
              def dfs(node: Optional[TreeNode]) -> int:
                  if not node:
                      return 0
                  # 正体递归
                  leftRet = dfs(node.left)
                  rightRet = dfs(node.right)
                  # 后处理
                  nonlocal ans
                  ans = max(ans, leftRet + rightRet + 1)
                  return max(leftRet, rightRet) + 1
      
              dfs(root)
              return ans - 1
      
    • 124. 二叉树中的最大路径和

      # Definition for a binary tree node.
      # class TreeNode:
      #     def __init__(self, val=0, left=None, right=None):
      #         self.val = val
      #         self.left = left
      #         self.right = right
      class Solution:
          def maxPathSum(self, root: Optional[TreeNode]) -> int:
              ans = -inf
              # dfs返回必定经过node且node为端点的最大路径和
              def dfs(node: Optional[TreeNode]) -> int:
                  if not node:
                      return 0
                  # 正体递归
                  leftRet = max(dfs(node.left), 0)
                  rightRet = max(dfs(node.right), 0)
                  # 后处理
                  nonlocal ans
                  ans = max(ans, leftRet + rightRet + node.val)
                  return max(leftRet, rightRet) + node.val
      
              dfs(root)
              return ans
      
    • 298. 二叉树最长连续序列

      # Definition for a binary tree node.
      # class TreeNode:
      #     def __init__(self, val=0, left=None, right=None):
      #         self.val = val
      #         self.left = left
      #         self.right = right
      class Solution:
          def longestConsecutive(self, root: Optional[TreeNode]) -> int:
              ans = 0
              # dfs返回必定经过node且node为端点的最长连续序列的长度
              def dfs(node: Optional[TreeNode]) -> int:
                  if not node:
                      return 0
                  # 正体递归
                  leftRet = dfs(node.left)
                  rightRet = dfs(node.right)
                  # 后处理
                  if node.left:
                      if node.left.val != node.val + 1:
                          leftRet = 0
                  if node.right:
                      if node.right.val != node.val + 1:
                          rightRet = 0
                  nonlocal ans
                  ans = max(ans, max(leftRet, rightRet) + 1)
                  return max(leftRet, rightRet) + 1
      
              dfs(root)
              return ans
      
    • 549. 二叉树中最长的连续序列

      # Definition for a binary tree node.
      # class TreeNode:
      #     def __init__(self, val=0, left=None, right=None):
      #         self.val = val
      #         self.left = left
      #         self.right = right
      class Solution:
          def longestConsecutive(self, root: Optional[TreeNode]) -> int:
              ans = 0
              # dfs返回必定经过node且node为端点的最长递增连续序列和最长递减序列的长度
              def dfs(node: Optional[TreeNode]) -> Tuple[int]:
                  if not node:
                      return (0, 0)
                  # 正体递归
                  leftInc, leftDcr = dfs(node.left)
                  rightInc, rightDcr = dfs(node.right)
                  # 后处理
                  inc, dcr = 1, 1
                  if node.left:
                      if node.left.val == node.val + 1:
                          inc = max(inc, leftInc + 1)
                      elif node.left.val == node.val - 1:
                          dcr = max(dcr, leftDcr + 1)
                  if node.right:
                      if node.right.val == node.val + 1:
                          inc = max(inc, rightInc + 1)
                      elif node.right.val == node.val - 1:
                          dcr = max(dcr, rightDcr + 1)
                  nonlocal ans
                  ans = max(ans, inc + dcr - 1)
                  return (inc, dcr)
      
              dfs(root)
              return ans
      
    • 687. 最长同值路径

      # Definition for a binary tree node.
      # class TreeNode:
      #     def __init__(self, val=0, left=None, right=None):
      #         self.val = val
      #         self.left = left
      #         self.right = right
      class Solution:
          def longestUnivaluePath(self, root: Optional[TreeNode]) -> int:
              ans = 0
              # dfs返回必定经过node且node为端点的最长同值路径
              def dfs(node: Optional[TreeNode]) -> int:
                  if not node:
                      return 0
                  # 正体递归
                  leftRet = dfs(node.left)
                  rightRet = dfs(node.right)
                  # 后处理
                  if node.left:
                      if node.left.val != node.val:
                          leftRet = 0
                  if node.right:
                      if node.right.val != node.val:
                          rightRet = 0
                  nonlocal ans
                  ans = max(ans, leftRet + rightRet + 1)
                  return max(leftRet, rightRet) + 1
      
              if not root: return 0
              dfs(root)
              return ans - 1
      
    • 1372. 二叉树中的最长交错路径

      # Definition for a binary tree node.
      # class TreeNode:
      #     def __init__(self, val=0, left=None, right=None):
      #         self.val = val
      #         self.left = left
      #         self.right = right
      class Solution:
          def longestZigZag(self, root: TreeNode) -> int:
              ans = 0
              # dfs返回必定经过node且node为端点的最长左-右-左和最长右-左-右路径的长度lrl和rlr
              def dfs(node: TreeNode) -> Tuple[int]:
                  if not node:
                      return (0, 0)
                  # 正体递归,lrl: left - right - left,rlr: right - left - right
                  leftlrl, leftrlr = dfs(node.left)
                  rightlrl, rightrlr = dfs(node.right)
                  # 后处理
                  lrl, rlr = 1, 1
                  if node.left:
                      lrl = 1 + leftrlr
                  if node.right:
                      rlr = 1 + rightlrl
                  nonlocal ans
                  ans = max(ans, max(lrl, rlr))
                  return (lrl, rlr)
      
              dfs(root)
              return ans - 1
      
    • 968. 监控二叉树

      # Definition for a binary tree node.
      # class TreeNode:
      #     def __init__(self, val=0, left=None, right=None):
      #         self.val = val
      #         self.left = left
      #         self.right = right
      class Solution:
          def minCameraCover(self, root: TreeNode) -> int:
              # a: node 必须放置摄像头的情况下,覆盖整棵树需要的摄像头数目
              # b: 覆盖整棵树需要的摄像头数目,无论 root 是否放置摄像头
              # c: 覆盖整棵树需要的摄像头数目,无论 root 本身是否被监控到
              def dfs(node: TreeNode) -> List[int]:
                  if not node: return [math.inf, 0, 0]
                  la, lb, lc = dfs(node.left)
                  ra, rb, rc = dfs(node.right)
                  a = lc + rc + 1
                  b = min(a, la + rb, ra + lb)
                  c = min(a, lb + rb)
                  return [a, b, c]
      
              a, b, c = dfs(root)
              return b
      
    • 865. 具有所有最深节点的最小子树

      # Definition for a binary tree node.
      # class TreeNode:
      #     def __init__(self, val=0, left=None, right=None):
      #         self.val = val
      #         self.left = left
      #         self.right = right
      class Solution:
          def subtreeWithAllDeepest(self, root: TreeNode) -> TreeNode:
              maxDepth = 0
              deepestNodes = set()
              # dfs用于获得最深节点的全体集合
              def dfs(node: TreeNode, depth: int):
                  if not node:
                      return
                  if not node.left and not node.right:
                      nonlocal maxDepth
                      if depth > maxDepth:
                          maxDepth = depth
                          deepestNodes.clear()
                          deepestNodes.add(node)
                      elif depth == maxDepth:
                          deepestNodes.add(node)
                      return
                  dfs(node.left, depth + 1)
                  dfs(node.right, depth + 1)
      
              dfs(root, 0)
              ans = None
              # dfs1返回node所在的子树中是否存在最深节点,处理类似于最近公共祖先
              def dfs1(node: TreeNode) -> bool:
                  if not node:
                      return False
                  nonlocal ans
                  if node in deepestNodes:
                      ans = node
                      return True
                  # 正体递归
                  leftRet = dfs1(node.left)
                  rightRet = dfs1(node.right)
                  # 后处理
                  if leftRet and rightRet:
                      ans = node
                      return True
                  elif leftRet or rightRet:
                      return True
                  return False
      
              dfs1(root)
              return ans
      
    • 1102. 得分最高的路径

      class Solution:
          def maximumMinimumPath(self, grid: List[List[int]]) -> int:
              m, n = len(grid), len(grid[0])
              dr = [-1,1,0,0]
              dc = [0,0,-1,1]
              # dfs返回采用最小值为mid的得分路径是否可以走通
              def dfs(r: int, c: int, mid: int) -> bool:
                  if r == m - 1 and c == n - 1:
                      return True
                  # 广义剪枝
                  if (r, c) in vis:
                      return False
                  vis.add((r , c))
                  for i in range(4):
                      nr, nc = r + dr[i], c + dc[i]
                      if 0 <= nr < m and 0 <= nc < n and grid[nr][nc] >= mid:
                          if dfs(nr, nc, mid):
                              return True
                  return False
      
              # 深度优先搜索 + 二分
              lo, hi = 0, min(grid[0][0], grid[m-1][n-1])
              while lo < hi:
                  mi = (lo + hi) // 2
                  vis = set()
                  if dfs(0, 0, mi):
                      if lo != mi:
                          lo = mi
                      else:
                          vis = set()
                          if dfs(0, 0, hi):
                              return hi
                          else:
                              return lo
                  else:
                      hi = mi - 1
              return lo
      
    • 1631. 最小体力消耗路径

      class Solution:
          def minimumEffortPath(self, heights: List[List[int]]) -> int:
              m, n = len(heights), len(heights[0])
              dr = [-1,1,0,0]
              dc = [0,0,-1,1]
              # dfs返回采用最小值为mid的体力路径是否可以走通
              def dfs(r: int, c: int, mid: int) -> bool:
                  if r == m - 1 and c == n - 1:
                      return True
                  # 广义剪枝
                  if (r, c) in vis:
                      return False
                  vis.add((r , c))
                  for i in range(4):
                      nr, nc = r + dr[i], c + dc[i]
                      if 0 <= nr < m and 0 <= nc < n and abs(heights[nr][nc]-heights[r][c]) <= mid:
                          if dfs(nr, nc, mid):
                              return True
                  return False
      
              # 深度优先搜索 + 二分
              lo, hi = 0, 10**6
              while lo < hi:
                  mi = (lo + hi) // 2
                  vis = set()
                  if dfs(0, 0, mi):
                      hi = mi
                  else:
                      lo = mi + 1
              return lo
      
    • 778. 水位上升的泳池中游泳

      class Solution:
          def swimInWater(self, grid: List[List[int]]) -> int:
              n = len(grid)
              dr = [-1,1,0,0]
              dc = [0,0,-1,1]
              # dfs返回采用最小值为mid的体力路径是否可以走通
              def dfs(r: int, c: int, mid: int) -> bool:
                  # 能到达最后一个格子,说明该水位可以成功
                  if r == n - 1 and c == n - 1:
                      return True
                  # 广义剪枝
                  if (r, c) in vis:
                      return False
                  vis.add((r, c))
                  for i in range(4):
                      nr, nc = r + dr[i], c + dc[i]
                      if 0 <= nr < n and 0 <= nc < n and grid[nr][nc] <= mid:
                          if dfs(nr, nc, mid):
                              return True
                  return False
      
              # 深度优先搜索 + 二分
              lo, hi = grid[0][0], n**2
              while lo < hi:
                  mi = (lo + hi) // 2
                  vis = set()
                  if dfs(0, 0, mi):
                      hi = mi
                  else:
                      lo = mi + 1
              return lo
      
    • 403. 青蛙过河

      class Solution:
          def canCross(self, stones: List[int]) -> bool:
              n = len(stones)
              stone2index = {}
              for i, v in enumerate(stones):
                  stone2index[v] = i
              @cache
              def dfs(idx: int, k: int) -> bool:
                  if idx >= n - 1:
                      return True
                  for nk in [k-1, k, k+1]:
                      if nk > 0 and stones[idx] + nk in stone2index:
                          next_idx = stone2index[stones[idx] + nk]
                          if dfs(next_idx, nk):
                              return True
                  return False
      
              return dfs(0, 0)