回溯算法 (Backtracking)

回溯算法 (Backtracking)

概述

难度:⭐⭐⭐ 分类:搜索与优化 (Search & Optimization)

回溯算法是一种通过枚举所有可能来找到问题解的算法思想。它采用试错的方式,在搜索过程中如果发现当前选择无法达成目标,就回退到上一步重新选择。

Backtracking is an algorithm paradigm that finds solutions by trying all possible candidates. It uses a trial-and-error approach, retreating when a path fails to lead to a valid solution.


核心思想

回溯 = 递归 + 剪枝

  1. 选择:从当前状态做出一个选择
  2. 探索:递归进入下一层
  3. 撤销:回退选择,尝试其他可能
  4. 剪枝:提前排除不可能的情况

经典应用

1. 全排列问题

def permute(nums):
    result = []
    def backtrack(path, used):
        if len(path) == len(nums):
            result.append(path[:])
            return
        for i in range(len(nums)):
            if used[i]:
                continue
            used[i] = True
            path.append(nums[i])
            backtrack(path, used)
            path.pop()
            used[i] = False
    backtrack([], [False] * len(nums))
    return result

2. N皇后问题

在 N×N 的棋盘上放置 N 个皇后,使得它们互不攻击。

def solveNQueens(n):
    result = []
    board = [['.' for _ in range(n)] for _ in range(n)]
    
    def is_valid(row, col):
        # 检查列
        for i in range(row):
            if board[i][col] == 'Q':
                return False
        # 检查左上角
        for i, j in zip(range(row-1, -1, -1), range(col-1, -1, -1)):
            if board[i][j] == 'Q':
                return False
        # 检查右上角
        for i, j in zip(range(row-1, -1, -1), range(col+1, n)):
            if board[i][j] == 'Q':
                return False
        return True
    
    def backtrack(row):
        if row == n:
            result.append([''.join(row) for row in board])
            return
        for col in range(n):
            if not is_valid(row, col):
                continue
            board[row][col] = 'Q'
            backtrack(row + 1)
            board[row][col] = '.'
    
    backtrack(0)
    return result

3. 子集问题

def subsets(nums):
    result = []
    def backtrack(start, path):
        result.append(path[:])
        for i in range(start, len(nums)):
            path.append(nums[i])
            backtrack(i + 1, path)
            path.pop()
    backtrack(0, [])
    return result

复杂度分析

  • 时间复杂度:O(n!) 或 O(2^n),通常需要剪枝优化
  • 空间复杂度:O(n),递归栈深度

适用条件

  • 问题可以分解为「选择 → 状态更新 → 回溯」的模式
  • 需要枚举所有可能的解
  • 解空间可以通过剪枝缩小

💡 记忆口诀

回溯算法三步走:选、搜、撤。 全排列用Used数组,N皇后检查三方向。 子集问题先加入,排列组合不混淆。

排列组合要分清,排列讲究顺序,组合不讲究顺序。