47. 全排列 II
题目来源:力扣(LeetCode)https://leetcode-cn.com/problems/permutations-ii
题目
给定一个可包含重复数字的序列,返回所有不重复的全排列。
示例:
输入: [1,1,2]
输出:
[
[1,1,2],
[1,2,1],
[2,1,1]
]
解题思路
思路:回溯
同样的,这里先建议尝试下面的题目,再回来看这道题。(若是下面的题目已经通过的可忽略)
在这道第 47 题中,与第 46 题不同的是:
- 给定的序列可包含重复数字;
- 但要求返回的全排列不能重复。
这里,其实这两个跟 40. 组合总和 II 的限制有点类似。但是 40 题重复组合,是指相同数字不同排序也视为重复;而本题,全排列重复指的是相同数字相同排序视为重复,这里要注意区分下。(毕竟排列跟组合有所区别😂,自我吐槽,可忽略)
首先,先看图示,如何去选择数字,返回全排列(这里只画其中一部分):
结合题目跟图示进行分析,我们可以发现每个数字其实是不能够重复使用的,那么在这里,要注意标记数字在前面是否已经被使用过,防止重复使用同个数字。
因为序列本身可包含重复数字,那么要如何避免返回的全排列不会重复?
这里可以考虑先将序列进行升序排序,排序后的序列,相同元素是相邻的,这里能够快速去重。
那么本题主要的思路如下:
- 首先先对序列进行升序排序,方便后面去重;
- 因为不允许全排列重复,那么递归当中每层不能存在重复的元素:
- 若同一层出现重复元素,那么重复元素后续向下递归的路径将会相同;
- 处理同层存在重复元素,这里我们可以设定优先考虑排在靠前的数字。
那么在这里,我们就需要考虑剪枝,首先是处理数字是否被使用的情况,用 used 列表标记;
if used[i]:
continue
如果数字在前面已被使用,那么这里跳过。
再来就是处理同层元素重复的情况,序列排序之后,重复数字相邻,优先考虑前面的数字:
if x > 0 and nums[i] == nums[i-1] and not used[i-1]:
continue
这里前面两个条件就是限定索引边界判断前后数字是否重复。最主要的是 not used[i-1]
这个条件,这里是一个前提,当出现数字重复时,前面的数字没被使用,后续相同的数字都跳过不予考虑,也就是优先考虑靠前的数字。
具体的实现代码如下。
class Solution:
def permuteUnique(self, nums: List[int]) -> List[List[int]]:
nums.sort()
ans = []
tmp = []
n = len(nums)
used = [False for _ in range(n)]
def helper(depth):
# 全排列选取元素结束,添加到返回列表,返回
if depth == n:
ans.append(tmp[:])
return
for i in range(n):
# 因为不希望重复,这里需要同一层不能选重复的元素
if i > 0 and nums[i] == nums[i-1] and not used[i-1]:
continue
# 数字已被使用,跳过
if used[i]:
continue
tmp.append(nums[i])
used[i] = True
helper(depth+1)
tmp.pop()
used[i] = False
helper(0)
return ans
欢迎关注
公众号 【书所集录】