文章目录
- 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里不包含重复元素,所以不需要考虑这个了。
那么写出来的代码就是这样的:
有没有什么问题呢,跑一下,果然有问题。因为每次都是从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,正好可以达到目的。
完整代码
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,这里不理解画个图就懂了。
所以对于排序后的数组,如果当前值和前一个值相同,则两种可能,
- 有可能是同一个分支的下层,也就是单个答案内的重复值,但不同的位置,此时不做处理。
- 另一种可能就是切换了分支了,是答案间的结果,此时需要去重。
两个去重放到一起就是:
完整代码