leetcode(力扣) 46. 全排列 & 47. 全排列 II(回溯 & 去重思路详解)

46.全排列

题目描述

给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。

示例 1:
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

示例 2:
输入:nums = [0,1]
输出:[[0,1],[1,0]]

示例 3:
输入:nums = [1]
输出:[[1]]

思路分析

又是一道经典回溯题,不过回溯类型和之前的有所区别,之前做的组合 和子集,今天这个是排列问题。
组合和排列的区别,排列里可以有[1,2],[2,1]这种,但是组合的话 这两种算一种。
所以在排列问题里,就不需要组合和子集里的startindex了。

老规矩, 回溯三步走;

1.确定函数参数:
这题没参数~~~~~哈哈

2.确定结束条件
画个树就能看出来,假设收集路径上的点的数组为path,答案集为res,则当res和path的长度一样时,代表达到叶子节点,也就是答案节点。

3.循环体

这道题就这里和之前不一样了,没有了startindex,循环直接从0到len(nums)。

题目中所给的nums里不包含重复元素,所以不需要考虑这个了。

那么写出来的代码就是这样的:

for i in range(len(nums)):      
     path.append(nums[i])
     backtrack()
     path.pop()

有没有什么问题呢,跑一下,果然有问题。因为每次都是从0开始,按照套路都知道,for是控制每一层的循环取值的,当进入循环递归到下一层的时候,i还是从0开始,会造成什么现象呢 ,就是出现[1,1,1]这样的结果,因为每次都是从0开始啊。

这时候如果找个startindex会出现什么情况呢,遍历到2的时候就是[2,3],而不会出现我们在排列里的[2,1,3] 这种组合了。因为startindex是告诉下一层遍历的起始点嘛。

所以这里实际上相当于舍弃startindex,转而加入了一种去重机制。

很简单,直接判断当前遍历的元素是否在记录的path中。如果在就不考虑这个元素了。 比如刚才的[1,1,1] 在不用startindex的循环中,每一层的每一次都会考虑所有的nums= [1,2,3]里的数值,第一次取1,下一层依旧从[1,2,3]里选,如果没有去重,则又会取1,加入去重之后选择了2,然后选3,正好可以达到目的。

完整代码

class Solution:
    def permute(self, nums: List[int]) -> List[List[int]]:
            res = []
            path = []
            def backtrack():
                if len(path) == len(nums):
                    res.append(path[:])
                    return
                for i in range(len(nums)):
                    if nums[i] in path:
                        continue
                    path.append(nums[i])
                    backtrack()
                    path.pop()
            backtrack()
            return  res

47.全排列 II

做这个题之前先做 40. 组合总和 II,这两道题的去重思路基本一样。

思路分析:

这道题在上一道的基础上做了一些更改,这道题所给的数组中有重复的数值了。

上一道题中所给的数组没有重复的值,所以使用非常简单的方法,直接判断了一下当前遍历的元素在path数组里有没有,没有的话再继续操作,而这道题没法用这个方法了,比如[1,1,2]这里有两个1,但是个合法的答案,因为这两个1是不同的元素,只是值相等。

所以面临两个问题。

  • 一个是要在答案内去重。 不同位置但值相同的元素 不算重复元素。
  • 一个是在答案间去重。

这里的去重思路和之前的组合总和2的去重思路差不多。

用一个temp数组记录当前遍历的值。
比如 temp = [False,False,False] nums = [1,1,2]
当遍历0号元素时, temp[0] = True ,那么在该0号元素的下面分支中(纵向),temp[0]都会是True,取0号元素之后的下一层取1号元素 那么temp[1] = True。
此时 temp = [True,True,False]。

这样的话就可以控制单个答案内不取重复元素了,取该元素之前只需要判断一下对应位置的temp是不是为True就行了。

接下来就是答案间的去重了,其实和之前组合总和2里判断startindex的思路一样。

答案间就意味着树切换分支了,在程序开始前先对所给数组排序,因为是排列问题,所以排序之后也没事,如果在遍历的时候检查到树切换分支了,那就意味着是另一个答案了,毕竟一个分支一个叶子节点,一个叶子节点一个答案。

如何判断是否切换分支了?
看temp数组,如果temp[i-1] = False 而temp[i] = True 则肯定是切换分支了,因为同一个分支下temp[i] 肯定也是True,这里不理解画个图就懂了。

所以对于排序后的数组,如果当前值和前一个值相同,则两种可能,

  • 有可能是同一个分支的下层,也就是单个答案内的重复值,但不同的位置,此时不做处理。
  • 另一种可能就是切换了分支了,是答案间的结果,此时需要去重。

两个去重放到一起就是:

if not temp[i]:  # 单个答案内去重
      if i>0 and nums[i] == nums[i-1] and not temp[i-1]: # 答案间去重

完整代码

class Solution:
    def permuteUnique(self, nums: List[int]) -> List[List[int]]:
            # res用来存放结果
            res = []
            temp = [False] * len(nums)
            path = []
            nums.sort()
            def backtrack():
                # 终止条件
                if len(path) == len(nums):
                    res.append(path[:])
                    return
                for i in range(len(nums)):
                    if not temp[i]:
                        if i>0 and nums[i] == nums[i-1] and not temp[i-1]:
                            continue
                        temp[i] = True
                        path.append(nums[i])
                        backtrack()
                        path.pop()
                        temp[i] = False
            backtrack()
            return res

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

深度不学习!!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值