BM56 有重复项数字的全排列¶
中等通过率 36.07%python3核心代码模式样例通过牛客 AC递归
讲解章节:DFS 深度优先搜索
一句话
排序后回溯,同层遇到「与前一个相同且前一个未被使用」就剪枝。
解题思路¶
这题考什么¶
骨架与 BM55 完全一致(选 -> 递归 -> 撤销,配 used 标记), 新增的唯一问题是重复数字会产出一模一样的排列: [1,1,2] 里两个 1 谁先谁后,写出来看不出区别,却会被算成两份。
先排序让相同的数字相邻,再在同一层里加一条剪枝:
读法是「同一组相同数字,必须按下标从左到右依次使用」。 展开说:num[j] 与 num[j-1] 相同而 num[j-1] 此刻没被占用,说明在当前这一层里 要么还没轮到 j-1(它在本层是另一条平行分支),要么它已经用完撤销了; 两种情况下选 j 都会重复出一份「先用后者」的排列,直接跳过。
条件必须是「前一个未被使用」:
去重回溯的这条剪枝见 记忆化搜索与剪枝。
数据规模与复杂度¶
0 < n <= 8,元素取值 -1 到 5,时限其他语言 2 秒。 互不相同的排列最多 8 的阶乘 = 40320 份,重复元素只会让它更少; 时间 O(n 乘 n 的阶乘),递归深度 8,空间 O(n)(答案不计)。
坑在哪¶
- 排序不能省,它同时担着两件事:让相同数字相邻,剪枝才抓得到; 让产出直接是字典序升序,而题面正是这么要求的,牛客严格比对顺序。
- 剪枝条件写反成 used[j-1] 也能去重,但代价是先走一段注定失败的分支: 那种写法要求同组数字从右往左用,于是「先选下标 0 的 1」这条路永远拼不完整, 白搜一层才回头。写成「未被使用」则在同层第一次就剪掉。
- 三个条件缺一不可。少了 j > 0 会越界访问 num[-1](Python 不报错, 而是悄悄取到末位元素,剪出错误结果);少了值相等的判断会把不同的数字也剪掉。
- 收答案照样要 path[:] 拷贝,理由同 BM55:path 是全程复用的同一个列表。
- 剪枝只在同一层生效,跨层由 used 负责,两者职责不同,不能互相替代。
样例复核¶
[1,1,2] 排序后仍是 [1,1,2]。
产出 [[1,1,2],[1,2,1],[2,1,1]],与样例的顺序逐字一致。
参考实现¶
[:octicons-arrow-left-16: BM55](BM55.md) [BM57 :octicons-arrow-right-16:](BM57.md)