> ## Content Index
> Fetch the complete content index at: https://blog.vercanti.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# 算法思路与模板
- URL: https://blog.vercanti.com/suan-fa-si-lu-yu-mo-ban/
- Published: 2026-08-28T14:35:45.000Z
- Updated: 2026-08-28T14:59:33.000Z
- Description: 二分查找要求序列有序，每次将搜索范围缩减一半，时间复杂度 O(log n)。 两个指针从两端向中间收缩，常用于有序数组。 滑动窗口维护一个满足条件的区间 left, right，right 不断向右扩张，条件不满足时收缩 left。 滑动窗口通用框架： 1. 确定"子问题"：原问题可以分解为哪些规模更小的同类问题 2. 定义 dpi 或 dpij 的含义，要足够清晰 3. 推导状态转移方程 4. 确定初始状态（边界条件） 5. 确定计算顺序（确保依赖的子问题先计算） 每件物品最多选一次。dpj = 容量为 j 时的最大价值，逆序遍历容量防止重复选取。 每
- Author: yellowdog
- Tags: 算法与数据结构

> 官方文档：<https://leetcode.cn/> | <https://oi-wiki.org/>  
> 适用范围：LeetCode 刷题、算法面试（2026-05-07 整理）

## 二分查找

二分查找要求序列有序，每次将搜索范围缩减一半，时间复杂度 O(log n)。

### 左闭右闭 `[left, right]`

```python
def binary_search(nums, target):
    left, right = 0, len(nums) - 1  # 右端点可取到

    while left <= right:             # 区间非空条件
        mid = left + (right - left) // 2  # 避免大数溢出（Python 无此问题，但习惯写法）
        if nums[mid] == target:
            return mid
        elif nums[mid] < target:
            left = mid + 1
        else:
            right = mid - 1

    return -1

```

### 左闭右开 `[left, right)`

```python
def binary_search_open(nums, target):
    left, right = 0, len(nums)  # 右端点不可取到

    while left < right:          # 区间非空条件
        mid = left + (right - left) // 2
        if nums[mid] == target:
            return mid
        elif nums[mid] < target:
            left = mid + 1
        else:
            right = mid          # 不是 mid - 1

    return -1

```

### 查找左边界（第一个 >= target 的位置）

```python
def lower_bound(nums, target):
    """
    返回 nums 中第一个 >= target 的索引。
    若所有元素 < target，返回 len(nums)。
    等价于 bisect.bisect_left(nums, target)。
    """
    left, right = 0, len(nums)

    while left < right:
        mid = left + (right - left) // 2
        if nums[mid] < target:
            left = mid + 1
        else:
            right = mid  # 可能是答案，但继续收缩右边界

    return left

```

### 查找右边界（最后一个 <= target 的位置）

```python
def upper_bound(nums, target):
    """
    返回 nums 中第一个 > target 的索引。
    等价于 bisect.bisect_right(nums, target)。
    最后一个 <= target 的位置为 upper_bound - 1。
    """
    left, right = 0, len(nums)

    while left < right:
        mid = left + (right - left) // 2
        if nums[mid] <= target:
            left = mid + 1
        else:
            right = mid

    return left

```

### Python bisect 模块

```python
import bisect

nums = [1, 3, 3, 5, 7]
bisect.bisect_left(nums, 3)   # 1，第一个 3 的位置
bisect.bisect_right(nums, 3)  # 3，最后一个 3 右侧的位置
bisect.insort_left(nums, 4)   # 原地插入，保持有序

```

### 二分查找的常见变体

- 在旋转排序数组中查找：先判断哪半段有序，再决定收缩方向
- 查找峰值：转化为"找第一个比右侧大的元素"
- 最小化最大值 / 最大化最小值：答案具有单调性时可二分答案

---

## 双指针

### 对向双指针

两个指针从两端向中间收缩，常用于有序数组。

```python
def two_sum_sorted(nums, target):
    """有序数组中找两数之和"""
    left, right = 0, len(nums) - 1
    while left < right:
        s = nums[left] + nums[right]
        if s == target:
            return [left, right]
        elif s < target:
            left += 1
        else:
            right -= 1
    return []

def is_palindrome(s):
    """判断字符串是否为回文"""
    left, right = 0, len(s) - 1
    while left < right:
        if s[left] != s[right]:
            return False
        left += 1
        right -= 1
    return True

```

### 同向双指针（快慢指针）

```python
def remove_duplicates(nums):
    """原地删除有序数组中的重复元素，返回新长度"""
    if not nums:
        return 0
    slow = 0
    for fast in range(1, len(nums)):
        if nums[fast] != nums[slow]:
            slow += 1
            nums[slow] = nums[fast]
    return slow + 1

def find_middle(head):
    """链表找中间节点（快慢指针）"""
    slow, fast = head, head
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
    return slow  # 偶数长度时返回右中间节点

```

### 滑动窗口模板

滑动窗口维护一个满足条件的区间 `[left, right]`，right 不断向右扩张，条件不满足时收缩 left。

```python
from collections import defaultdict

def sliding_window(s, pattern):
    """
    找到 s 中包含 pattern 所有字符的最小子串（最小覆盖子串）。
    时间复杂度 O(n)。
    """
    need = defaultdict(int)
    for c in pattern:
        need[c] += 1

    window = defaultdict(int)
    left = right = 0
    valid = 0          # window 中满足 need 条件的字符数
    start, min_len = 0, float('inf')

    while right < len(s):
        # 扩张窗口
        c = s[right]
        right += 1
        if c in need:
            window[c] += 1
            if window[c] == need[c]:
                valid += 1

        # 收缩窗口
        while valid == len(need):
            if right - left < min_len:
                start = left
                min_len = right - left
            d = s[left]
            left += 1
            if d in need:
                if window[d] == need[d]:
                    valid -= 1
                window[d] -= 1

    return s[start:start + min_len] if min_len != float('inf') else ""

```

滑动窗口通用框架：

```python
def sliding_window_template(nums):
    left = 0
    window_state = {}   # 记录窗口内状态
    result = ...        # 根据题目初始化

    for right in range(len(nums)):
        # 1. 将 nums[right] 加入窗口
        # window_state 更新

        # 2. 判断是否需要收缩左边界
        while window_invalid(window_state):
            # 将 nums[left] 移出窗口
            # window_state 更新
            left += 1

        # 3. 更新答案
        # result = update(result, right - left + 1)

    return result

```

---

## 动态规划

### 状态定义思路

1. 确定"子问题"：原问题可以分解为哪些规模更小的同类问题
2. 定义 `dp[i]` 或 `dp[i][j]` 的含义，要足够清晰
3. 推导状态转移方程
4. 确定初始状态（边界条件）
5. 确定计算顺序（确保依赖的子问题先计算）

### 线性 DP

```python
def longest_increasing_subsequence(nums):
    """
    最长递增子序列（LIS），O(n²) 版本。
    dp[i] = 以 nums[i] 结尾的 LIS 长度。
    """
    n = len(nums)
    dp = [1] * n
    for i in range(1, n):
        for j in range(i):
            if nums[j] < nums[i]:
                dp[i] = max(dp[i], dp[j] + 1)
    return max(dp)

def longest_common_subsequence(text1, text2):
    """
    最长公共子序列（LCS），O(m*n)。
    dp[i][j] = text1[:i] 与 text2[:j] 的 LCS 长度。
    """
    m, n = len(text1), len(text2)
    dp = [[0] * (n + 1) for _ in range(m + 1)]
    for i in range(1, m + 1):
        for j in range(1, n + 1):
            if text1[i - 1] == text2[j - 1]:
                dp[i][j] = dp[i - 1][j - 1] + 1
            else:
                dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
    return dp[m][n]

```

### 背包问题

#### 01 背包

每件物品最多选一次。`dp[j]` \= 容量为 j 时的最大价值，逆序遍历容量防止重复选取。

```python
def knapsack_01(weights, values, capacity):
    """
    dp[j] = 容量恰好为 j（或至多为 j）时的最大价值。
    """
    dp = [0] * (capacity + 1)
    for i in range(len(weights)):
        # 逆序遍历：确保每件物品只被选一次
        for j in range(capacity, weights[i] - 1, -1):
            dp[j] = max(dp[j], dp[j - weights[i]] + values[i])
    return dp[capacity]

```

#### 完全背包

每件物品可选无限次，正序遍历容量。

```python
def knapsack_complete(weights, values, capacity):
    dp = [0] * (capacity + 1)
    for i in range(len(weights)):
        # 正序遍历：允许重复选取
        for j in range(weights[i], capacity + 1):
            dp[j] = max(dp[j], dp[j - weights[i]] + values[i])
    return dp[capacity]

```

| 背包类型  | 遍历物品 | 遍历容量  | 特点                |
| ----- | ---- | ----- | ----------------- |
| 01 背包 | 外层   | 内层，逆序 | 每件选 0 或 1 次       |
| 完全背包  | 外层   | 内层，正序 | 每件可选任意次           |
| 多重背包  | 外层   | 内层，逆序 | 每件最多选 k 次，可拆分为 01 |

### 区间 DP

区间 DP 的状态为区间 `[i, j]`，通常先枚举区间长度，再枚举左端点。

```python
def matrix_chain_multiplication(dims):
    """
    矩阵链乘法：dims[i-1] x dims[i] 为第 i 个矩阵的尺寸。
    dp[i][j] = 计算矩阵 i 到 j 的最小乘法次数。
    """
    n = len(dims) - 1
    dp = [[0] * n for _ in range(n)]

    for length in range(2, n + 1):         # 枚举区间长度
        for i in range(n - length + 1):    # 枚举左端点
            j = i + length - 1             # 右端点
            dp[i][j] = float('inf')
            for k in range(i, j):          # 枚举分割点
                cost = dp[i][k] + dp[k + 1][j] + dims[i] * dims[k + 1] * dims[j + 1]
                dp[i][j] = min(dp[i][j], cost)

    return dp[0][n - 1]

```

### 状态压缩 DP

当状态集合规模不大（通常 n <= 20）时，用整数的二进制位表示子集。

```python
def traveling_salesman(dist):
    """
    旅行商问题（TSP），dp[mask][i] = 访问了 mask 表示的城市子集，
    当前在城市 i 时的最短路径。
    """
    n = len(dist)
    INF = float('inf')
    dp = [[INF] * n for _ in range(1 << n)]
    dp[1][0] = 0  # 只访问了城市 0，在城市 0

    for mask in range(1 << n):
        for u in range(n):
            if dp[mask][u] == INF:
                continue
            if not (mask >> u & 1):
                continue
            for v in range(n):
                if mask >> v & 1:  # v 已访问
                    continue
                new_mask = mask | (1 << v)
                dp[new_mask][v] = min(dp[new_mask][v], dp[mask][u] + dist[u][v])

    full_mask = (1 << n) - 1
    return min(dp[full_mask][i] + dist[i][0] for i in range(n))

```

### 空间优化（滚动数组）

当 `dp[i]` 只依赖 `dp[i-1]` 时，可将二维数组压缩为一维（01背包已展示）。

依赖前两行时用两个交替数组：

```python
# dp[i][j] 依赖 dp[i-1][...] 时
prev = [0] * (n + 1)
curr = [0] * (n + 1)

for i in range(1, m + 1):
    for j in range(1, n + 1):
        curr[j] = ...  # 使用 prev[j], prev[j-1], curr[j-1]
    prev, curr = curr, prev  # 交换

```

---

## 回溯

回溯是在决策树上的 DFS，通过"选择 -> 递归 -> 撤销选择"枚举所有可能。

### 通用模板

```python
def backtrack(path, choices):
    if is_solution(path):
        result.append(path[:])  # 注意深拷贝
        return

    for choice in choices:
        if is_valid(choice, path):
            path.append(choice)        # 做选择
            backtrack(path, new_choices)
            path.pop()                 # 撤销选择

```

### 排列

```python
def permutations(nums):
    result = []
    used = [False] * len(nums)

    def backtrack(path):
        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)
            path.pop()
            used[i] = False

    backtrack([])
    return result

```

### 组合

```python
def combinations(n, k):
    result = []

    def backtrack(start, path):
        if len(path) == k:
            result.append(path[:])
            return
        # 剪枝：剩余元素不足以填满 path 时停止
        for i in range(start, n - (k - len(path)) + 2):
            path.append(i)
            backtrack(i + 1, path)
            path.pop()

    backtrack(1, [])
    return result

```

### 子集

```python
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

```

### 剪枝策略

| 场景          | 剪枝方法                                                                            |
| ----------- | ------------------------------------------------------------------------------- |
| 组合去重（含重复元素） | 排序后跳过相邻重复元素：if i > start and nums\[i\] == nums\[i-1\]: continue                 |
| 排列去重        | 排序 + 同层跳过重复：if i > 0 and nums\[i\] == nums\[i-1\] and not used\[i-1\]: continue |
| 路径和超限       | 提前 return：if current\_sum > target: return                                      |
| 组合填充不足      | 剩余元素个数检查：n - i + 1 < k - len(path) 时停止                                          |

---

## 贪心

### 适用场景判断

贪心策略成立需要满足**贪心选择性质**：每一步的局部最优选择不影响后续子问题的最优性。

常见贪心问题特征：

| 问题类型 | 典型题目        | 贪心策略             |
| ---- | ----------- | ---------------- |
| 区间调度 | 最多不重叠区间     | 按区间右端点升序排列，尽早结束  |
| 区间覆盖 | 覆盖区间所需最少区间数 | 按左端点排序，每次选择覆盖最远的 |
| 跳跃游戏 | 能否到达末尾      | 维护当前可达最远位置       |
| 任务调度 | 最小化总等待时间    | 按任务时间升序排列        |

判断贪心是否正确：可用**数学归纳法**或**反证法**证明，或构造反例推翻。无法证明时考虑 DP。

---

## 排序算法

### 时间/空间复杂度与稳定性

| 算法            | 平均时间        | 最差时间       | 空间       | 稳定 | 备注              |
| ------------- | ----------- | ---------- | -------- | -- | --------------- |
| 冒泡排序          | O(n²)       | O(n²)      | O(1)     | 是  | 教学用             |
| 选择排序          | O(n²)       | O(n²)      | O(1)     | 否  | 教学用             |
| 插入排序          | O(n²)       | O(n²)      | O(1)     | 是  | 小数组/近乎有序时高效     |
| 希尔排序          | O(n log² n) | O(n²)      | O(1)     | 否  | 插入排序改进          |
| 归并排序          | O(n log n)  | O(n log n) | O(n)     | 是  | 链表排序首选          |
| 快速排序          | O(n log n)  | O(n²)      | O(log n) | 否  | 实践中最快，随机化规避最坏   |
| 堆排序           | O(n log n)  | O(n log n) | O(1)     | 否  | 原地，不如快排缓存友好     |
| 计数排序          | O(n + k)    | O(n + k)   | O(k)     | 是  | 值域 k 较小的整数      |
| 基数排序          | O(n \* d)   | O(n \* d)  | O(n + k) | 是  | d 为最大位数         |
| Python sorted | O(n log n)  | O(n log n) | O(n)     | 是  | Timsort，归并+插入混合 |

> Python 内置 `sorted()` 和 `list.sort()` 使用 Timsort，实际使用中直接调用即可。

### 快速排序

```python
import random

def quick_sort(nums, left, right):
    if left >= right:
        return
    pivot_idx = partition(nums, left, right)
    quick_sort(nums, left, pivot_idx - 1)
    quick_sort(nums, pivot_idx + 1, right)

def partition(nums, left, right):
    # 随机化 pivot，规避最坏情况
    rand_idx = random.randint(left, right)
    nums[rand_idx], nums[right] = nums[right], nums[rand_idx]

    pivot = nums[right]
    i = left - 1  # i 指向小于 pivot 区域的右边界

    for j in range(left, right):
        if nums[j] <= pivot:
            i += 1
            nums[i], nums[j] = nums[j], nums[i]

    nums[i + 1], nums[right] = nums[right], nums[i + 1]
    return i + 1

```

快速选择（第 K 大元素）：

```python
def find_kth_largest(nums, k):
    """
    平均 O(n) 找第 k 大元素（1-indexed）。
    """
    def quick_select(left, right, target_idx):
        if left == right:
            return nums[left]
        pivot_idx = partition(nums, left, right)
        if pivot_idx == target_idx:
            return nums[pivot_idx]
        elif pivot_idx < target_idx:
            return quick_select(pivot_idx + 1, right, target_idx)
        else:
            return quick_select(left, pivot_idx - 1, target_idx)

    return quick_select(0, len(nums) - 1, len(nums) - k)

```

### 归并排序

```python
def merge_sort(nums):
    if len(nums) <= 1:
        return nums

    mid = len(nums) // 2
    left = merge_sort(nums[:mid])
    right = merge_sort(nums[mid:])
    return merge(left, right)

def merge(left, right):
    result = []
    i = j = 0
    while i < len(left) and j < len(right):
        if left[i] <= right[j]:
            result.append(left[i])
            i += 1
        else:
            result.append(right[j])
            j += 1
    result.extend(left[i:])
    result.extend(right[j:])
    return result

```

---

## LeetCode 高频题分类

### 数组

| 方法   | 代表题          | 要点                                                                            |
| ---- | ------------ | ----------------------------------------------------------------------------- |
| 前缀和  | 区间求和、子数组和为 K | prefix\[i\] = nums\[0\] + ... + nums\[i-1\]，区间和 = prefix\[r+1\] - prefix\[l\] |
| 差分数组 | 区间加减操作       | diff\[i\] = nums\[i\] - nums\[i-1\]，还原用前缀和                                    |
| 双指针  | 三数之和、接雨水     | 先排序，再对向/同向指针                                                                  |
| 滑动窗口 | 无重复字符最长子串    | 维护窗口内状态，right 扩张 + left 收缩                                                    |

### 字符串

| 方法            | 代表题          | 要点                         |
| ------------- | ------------ | -------------------------- |
| 双指针           | 反转字符串、回文判断   | 原地反转                       |
| 滑动窗口          | 最小覆盖子串、字母异位词 | 用字典记录字符频次                  |
| KMP / 内置 find | 字符串匹配        | Python 直接用 in 或 str.find() |
| 动态规划          | 最长回文子串、编辑距离  | 二维 dp 表                    |

### 树

| 遍历方式    | 模板            | 适用场景      |
| ------- | ------------- | --------- |
| 前序（根左右） | 递归 / 迭代（栈）    | 序列化、路径问题  |
| 中序（左根右） | 递归 / 迭代（栈）    | BST 升序输出  |
| 后序（左右根） | 递归 / 迭代（反转前序） | 删除节点、路径和  |
| 层序（BFS） | 队列            | 最小深度、锯齿遍历 |

```python
from collections import deque

def level_order(root):
    """层序遍历，返回每层节点值的列表"""
    if not root:
        return []
    result = []
    queue = deque([root])
    while queue:
        level_size = len(queue)
        level = []
        for _ in range(level_size):
            node = queue.popleft()
            level.append(node.val)
            if node.left:
                queue.append(node.left)
            if node.right:
                queue.append(node.right)
        result.append(level)
    return result

```

### 动态规划高频题

| 题型      | 代表题      | DP 定义                                           |
| ------- | -------- | ----------------------------------------------- |
| 爬楼梯     | 斐波那契变体   | dp\[i\] = dp\[i-1\] + dp\[i-2\]                 |
| 打家劫舍    | 不相邻选择最大和 | dp\[i\] = max(dp\[i-1\], dp\[i-2\] + nums\[i\]) |
| 最长公共子序列 | LCS      | dp\[i\]\[j\]，见[线性 DP](#%E7%BA%BF%E6%80%A7-dp)   |
| 最长递增子序列 | LIS      | dp\[i\]，见[线性 DP](#%E7%BA%BF%E6%80%A7-dp)        |
| 01 背包   | 分割等和子集   | 见[01 背包](#01-%E8%83%8C%E5%8C%85)                |
| 完全背包    | 零钱兑换     | 见[完全背包](#%E5%AE%8C%E5%85%A8%E8%83%8C%E5%8C%85)  |
| 编辑距离    | 单词转换最少操作 | dp\[i\]\[j\] = min(增, 删, 替换)                    |

### 图（拓扑/最短路）

| 算法           | 适用场景       | 复杂度              |
| ------------ | ---------- | ---------------- |
| BFS 最短路      | 无权图        | O(V + E)         |
| Dijkstra     | 非负权图       | O((V + E) log V) |
| Bellman-Ford | 含负权（检测负环）  | O(VE)            |
| 拓扑排序         | 检测有向环、课程先修 | O(V + E)         |

---

## 踩坑与注意事项

- Python 整数无溢出，但浮点数比较应用 `math.isclose`，不要直接 `==`。
- 回溯将列表加入结果时必须 `result.append(path[:])`，不能直接 `append(path)`，否则所有结果指向同一列表。
- 动态规划初始化二维数组用列表推导 `[[0]*n for _ in range(m)]`，不能用 `[[0]*n]*m`（所有行共享引用）。
- 二分查找中 `mid = (left + right) // 2` 可能导致其他语言整数溢出，Python 不受影响，但养成写 `left + (right - left) // 2` 的习惯便于跨语言复用。
- 快速排序最坏情况为 O(n²)（已排序数组 + 固定 pivot），面试实现时务必加随机化 pivot。
- 记忆化搜索（自顶向下 DP）可直接用 `@functools.lru_cache(maxsize=None)` 装饰递归函数，避免手动维护缓存字典。

---

## 最佳实践

### 解题框架：拿到题先问自己这几个问题

1. **数据规模**：n 是多少？决定允许的时间复杂度（n≤20 → 指数；n≤1000 → O(n²)；n≤10⁶ → O(n log n) 或 O(n)）
2. **问最优还是计数**？最优值 → 动态规划；可行性 → 贪心/BFS；计数 → 组合数学/DP
3. **数据是否有序**？有序 → 二分；无序且频繁查找 → 排序或哈希
4. **是否需要回溯**？求所有解/排列组合 → 回溯；子集类问题 → 位运算枚举

### 算法选型速查

| 问题类型            | 推荐算法            | 时间复杂度          |
| --------------- | --------------- | -------------- |
| 有序数组查找 / 最小满足条件 | 二分查找            | O(log n)       |
| 连续子数组最大和        | Kadane 算法（滑动窗口） | O(n)           |
| 无重复字符最长子串       | 滑动窗口 + 哈希       | O(n)           |
| 两数之和 / 三数之和     | 哈希表 / 排序 + 双指针  | O(n) / O(n²)   |
| 全排列 / 子集 / 组合   | 回溯              | O(n!) / O(2^n) |
| 最短路（无权图）        | BFS             | O(V+E)         |
| 最短路（有权图）        | Dijkstra + 堆    | O((V+E)logV)   |
| 拓扑排序 / 检测环      | BFS（Kahn）/ DFS  | O(V+E)         |
| 区间合并            | 排序 + 贪心         | O(n log n)     |
| 最长递增子序列（LIS）    | DP + 二分         | O(n log n)     |
| 最长公共子序列（LCS）    | DP              | O(mn)          |
| 字符串匹配           | KMP             | O(n+m)         |

### DP 状态设计套路

```
dp[i]    = 考虑前 i 个元素时的最优解（线性 DP）
dp[i][j] = 考虑前 i 个、前 j 个时的最优解（区间/匹配 DP）
dp[mask] = 集合状态为 mask 时的最优解（状压 DP）

```

**转移方程写法**：先定义"最后一步做了什么"，再枚举最后一步的所有可能。

### 面试代码规范

- 先用简单示例手动验证思路，再写代码
- 边界条件：空输入、单元素、全相同元素
- 命名清晰：用 `left/right` 而非 `i/j`、用 `result` 而非 `res`（面试可用缩写）
- 复杂度分析：写完代码后主动说时间和空间复杂度

---

## 常见陷阱

### 陷阱：回溯时直接 `append(path)` 导致结果全为空

**现象：** 回溯搜索完成后，`result` 中所有子列表都是空的（或内容相同），明明打印过正确路径却没有保存下来。

**原因：** Python 列表是引用类型，`result.append(path)` 保存的是同一个 `path` 对象的引用。回溯过程中 `path` 被反复修改，所有已保存的引用最终指向同一个空（或最终状态）列表。

**解决：** 加入结果时拷贝一份：`result.append(path[:])`。

```python
def backtrack(path, start):
    if condition:
        result.append(path[:])  # 浅拷贝，不是 append(path)
        return
    for i in range(start, n):
        path.append(nums[i])
        backtrack(path, i + 1)
        path.pop()

```

### 陷阱：二维 DP 数组用乘法初始化导致所有行共享引用

**现象：** 修改 `dp[0][0]` 后，`dp[1][0]`、`dp[2][0]` 等也随之改变，DP 结果完全错误。

**原因：** `[[0] * n] * m` 创建的是 m 个指向同一子列表的引用，不是 m 个独立的列表。

**解决：** 用列表推导式创建二维数组。

```python
# 错误：所有行共享同一内存
dp = [[0] * n] * m

# 正确：每行独立
dp = [[0] * n for _ in range(m)]

```

### 陷阱：二分查找死循环（循环条件或 mid 计算错误）

**现象：** `while left <= right` 的二分搜索死循环；或找到目标但返回了错误的索引。

**原因：** 最常见的两种错误：① 边界更新写成 `left = mid` 或 `right = mid`（不含 +1/-1 步进）导致死循环；② 混用开/闭区间模板（半开区间 `[left, right)` 与闭区间 `[left, right]` 的终止条件和边界更新不同）。

**解决：** 选一种模板并严格遵守。最常用的闭区间模板：

```python
left, right = 0, len(nums) - 1
while left <= right:
    mid = left + (right - left) // 2
    if nums[mid] == target:
        return mid
    elif nums[mid] < target:
        left = mid + 1   # 必须 +1
    else:
        right = mid - 1  # 必须 -1
return -1

```

---

## 参见

- [数据结构完全参考](https://blog.vercanti.com/shu-ju-jie-gou-wan-quan-can-kao/) — 链表、树、图、堆的实现与操作
- [Python高级面试题](https://blog.vercanti.com/python-gao-ji-mian-shi-ti/) — 算法题 Python 实现技巧
- [itertools与functools完全指南](https://blog.vercanti.com/itertools-yu-functools-wan-quan-zhi-nan/) — `lru_cache`、`reduce`、`combinations` 等工具