本文分享一些自己在刷回溯算法-
子集组合排列
时总结的套路。
分类规则:原数组中的元素是否重复 | 数组中的元素是否可以复用
相关题目参考LeetCode78、77、46:
相关题目参考LeetCode90、40、47:
相关题目参考LeetCode39:
本节将子集组合排列模块化,在以后做题时根据题目要求选取模块组装即可,需要做过一些子集组合排列相关回溯题目,对其有一定理解。
List<List<Integer>> res = new LinkedList<>(); LinkedList<Integer> track = new LinkedList<>(); void backtrack(arg...) { //根据条件将路径加入结果集 if(条件判断) { res.add(new LinkedList<>(track)); return; } for(循环条件) { track.add(数值); backtrack(arg...); track.removeLast(); } }
子集:求全部的子集,不需要条件判断
组合:需要条件判断,一般判断是track.size() == k
即符合组合大小K的路径才会添加到结果集
排列:需要条件判断,一般判断是track.size() == nums.length
即将数组中的元素全部添加的路径才会添加到结果集
分析:组合和排列的判断条件本质上是一样的,都是取某一层的结果。
子集:for(int i=start; i<nums.length; i++) + backtrack(nums,i+1)
组合:for(int i=start; i<nums.length; i++)/for(int i=start; i<nums.length-(k-track.size())+1; i++) + backtrack(nums,i+1)
排列:for(int i=0; i<nums.length; i++) + if(used[i]) {continue;}
分析:for循环的功能:1.遍历数组 2.在子集和组合问题和backtrack(nums,i+1)配合防止元素被重复使用。 3.在排列问题中和if(used[i]) {continue;}配合防止重复选择
子集:(1)表征数组:int[] nums或者int n (2)表征start:int start
组合:(1)表征数组:int[] nums或者int n (2)表征start:int start (3)表征取第几层集合([]集合是第一层):int k
排列:(1)表征数组:int[] nums
子集:(1)
Arrays.sort(nums)
:排序,让相同的元素靠在一起 (2)在for循环中假如if(i>start && nums[i] == nums[i-1]) {continue;}:i>start
使nums[i-1]存在;nums[i] == nums[i-1]
跳过相同的元素
组合:同子集
排列:跟子集略有不同:if(i>start && nums[i] == nums[i-1] && !used[i-1]) {continue;}:新增!used[i-1]
是为了固定元素的相对顺序,防止出现重复结果
此模块与模块2+4相反,模块2的条件都是为了元素不被复用
子集:backtrack(nums,i+1)改为backtrack(nums,i),且不加模块4的条件
组合:同子集
排列:去掉used剪枝逻辑
子集和组合可以归为一类,backtrack(nums,i+1)是防止同一元素被多次使用,if(i>start && nums[i] == nums[i-1]) 是防止出现重复的结果。
排序单独一类:if(used[i]) {continue;}是防止同一元素被多次使用,!used[i-1]是防止出现重复结果。
排序实则是对子集和组合结果的进一步划分,所以比子集组合的条件更加苛刻。
leetcode-78:子集
leetcode-77:组合
leetcode-46:全排列
leetcode-90:子集II
leetcode-40:组合总和II
leetcode-47:全排列II
leetcode-39:组合总和
声明:本文章参考了东哥的算法小抄,随笔中有很多地方都没有都没有具体解释,主要是作为自己的回顾随笔,如果有不清楚的,可以先看东哥的算法小抄。