LeetCode 47. 全排列 II | Python

47. 全排列 II


题目来源:力扣(LeetCode)https://leetcode-cn.com/problems/permutations-ii

题目


给定一个可包含重复数字的序列,返回所有不重复的全排列。

示例:

输入: [1,1,2]
输出:
[
  [1,1,2],
  [1,2,1],
  [2,1,1]
]

解题思路


思路:回溯

同样的,这里先建议尝试下面的题目,再回来看这道题。(若是下面的题目已经通过的可忽略)

46. 全排列

在这道第 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

欢迎关注


公众号 【书所集录

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值