PrepStack · Markdown HTML

排列回溯

回溯与约束搜索 · HTML

数学 / 逻辑形态

dfs(pos) 为位置 pos 选择任一未使用索引 i,标记 used[i],递归 pos+1,再撤销。输入排序后,若 nums[i]==nums[i-1] 且前一个相同值尚未在本路径使用,则跳过。

识别信号

容易误判的相似信号

SignalWhy it is different
顺序不重要的选取应使用 start 组合回溯,排列会产生 k! 重复顺序。
只求排列数量可用阶乘/多重集合公式或 DP,无需生成。
位置选择具有局部最优结构若只求最佳排列,可能有贪心、DP 或 assignment 算法,不必列出所有。

核心不变量

进入深度 pos 时,path 恰含 pos 个互异索引;used 精确对应 path,且同层相等值只由第一个合法代表扩展。

状态、转移与处理顺序

ItemDefinition
State当前位置 pos、path、used 布尔数组;或通过交换把 [0,pos) 作为已固定前缀。
Transition选择一个未用索引,标记并加入;递归后按相反顺序移除并取消标记。
Frontier / order排列树的一层代表一个输出位置;分支数随剩余元素递减。

Python 骨架

def unique_permutations(nums):
    nums = sorted(nums)
    used = [False] * len(nums)
    path, out = [], []
    def dfs():
        if len(path) == len(nums):
            out.append(path.copy())
            return
        for i, value in enumerate(nums):
            if used[i]: continue
            if i and nums[i] == nums[i - 1] and not used[i - 1]:
                continue
            used[i] = True; path.append(value)
            dfs()
            path.pop(); used[i] = False
    dfs()
    return out

正确性思路

  1. used 保证一条根到叶路径每个索引恰用一次,因此叶子是合法排列。
  2. 对不同值,循环能在每位置选择任一剩余值,故所有排列可达。
  3. 相等值中只允许当前路径最早未处理代表先被选,消除交换相同值产生的重复而不移除任何值序列。

复杂度

TimeSpaceParameters
无重复时 Θ(n·n!),输出复制占主导O(n) 栈/path/used,不计输出n 为元素数;重复值将输出数降为 n!/∏c_i!。

边界条件

错误模式

PatternWhy it fails / fix
用 start 代替 usedstart 固定索引递增,只生成组合而非排列。
重复值去重条件反了应跳过前一个相同值未被使用的情况,保证同层代表顺序。
used 未撤销后续兄弟分支永久丢失候选。
先生成再用 set 去重浪费指数分支和大量内存;在树上剪掉等价分支。

选择与排除规则

相关模板

RelationTemplateBoundary
specialization约束满足回溯变量是位置、域是尚未使用元素。
contrast子集 / 组合回溯顺序不重要,用 start 规范化。

原创微型例子

环节内容
Result得到 AAB、ABA、BAA,恰各一次。
Setup徽章值 [A,A,B] 的唯一排列。
Trace根层若第一个 A 未用,第二个 A 被跳过;在更深层前一 A 已用时,第二 A 可选。

Recall prompts

我的补充