代码随想录算法Day29 | 491.递增子序列 ,46.全排列 ,47.全排列 II
491.递增子序列
题目链接:491. 递增子序列 - 力扣(LeetCode)
思路
这道题和 90.集合II 类似。不同的是这道题不能通过排序使相同元素聚集在一起,因此不能使用 used数组 或者 startIndex 的方法去重。
在这道题中,虽然我们不可以让相同元素聚集在一起,但是我们可以通过 map 或 数组来当哈希(题目数值范围[-100,100])来记录当前
树层已经是否已经使用该元素。
注意,进入新一层的时候要重新定义(清空),map 或 数组当哈希 只负责记录本次是否已经访问过。
详细如图所示。

代码
1 // 使用数组当map 2 class Solution { 3 private List<Integer> path = new ArrayList<>(); 4 private List<List<Integer>> res = new ArrayList<>(); 5 public List<List<Integer>> findSubsequences(int[] nums) { 6 backtracking(nums,0); 7 return res; 8 } 9 10 private void backtracking (int[] nums, int start) { 11 if (path.size() > 1) { 12 res.add(new ArrayList<>(path)); 13 } 14 15 int[] used = new int[201]; 16 for (int i = start; i < nums.length; i++) { 17 if (!path.isEmpty() && nums[i] < path.get(path.size() - 1) || 18 (used[nums[i] + 100] == 1)) continue; 19 used[nums[i] + 100] = 1; 20 path.add(nums[i]); 21 backtracking(nums, i + 1); 22 path.remove(path.size() - 1); 23 } 24 } 25 }
1 //法二:使用map 2 class Solution { 3 //结果集合 4 List<List<Integer>> res = new ArrayList<>(); 5 //路径集合 6 LinkedList<Integer> path = new LinkedList<>(); 7 public List<List<Integer>> findSubsequences(int[] nums) { 8 getSubsequences(nums,0); 9 return res; 10 } 11 private void getSubsequences( int[] nums, int start ) { 12 if(path.size()>1 ){ 13 res.add( new ArrayList<>(path) ); 14 // 注意这里不要加return,要取树上的节点 15 } 16 HashMap<Integer,Integer> map = new HashMap<>(); 17 for(int i=start ;i < nums.length ;i++){ 18 if(!path.isEmpty() && nums[i]< path.getLast()){ 19 continue; 20 } 21 // 使用过了当前数字 22 if ( map.getOrDefault( nums[i],0 ) >=1 ){ 23 continue; 24 } 25 map.put(nums[i],map.getOrDefault( nums[i],0 )+1); 26 path.add( nums[i] ); 27 getSubsequences( nums,i+1 ); 28 path.removeLast(); 29 } 30 } 31 }
46.全排列
题目链接:46. 全排列 - 力扣(LeetCode)
思路
首先通过题意可知排列是有序的,也就是说 [1,2] 和 [2,1] 是两个集合,这和之前分析的子集以及组合所不同的地方。
因此每次循坏都要从零开始,而在遍历的时候就需要 “树枝去重”。
树枝去重关键点在于used数组的使用。在used数组中 我们把上层已经遍历过的元素 记为 true ,因此在遍历的时候
只要used数组为true,就直接进入到下一层。
具体过程如下

注意,回溯的时候也要把used一并回溯。
代码
1 // 使用used数组 2 class Solution { 3 4 List<List<Integer>> result = new ArrayList<>();// 存放符合条件结果的集合 5 LinkedList<Integer> path = new LinkedList<>();// 用来存放符合条件结果 6 boolean[] used; 7 public List<List<Integer>> permute(int[] nums) { 8 if (nums.length == 0){ 9 return result; 10 } 11 used = new boolean[nums.length]; 12 permuteHelper(nums); 13 return result; 14 } 15 16 private void permuteHelper(int[] nums){ 17 if (path.size() == nums.length){ 18 result.add(new ArrayList<>(path)); 19 return; 20 } 21 for (int i = 0; i < nums.length; i++){ 22 if (used[i]){ 23 continue; 24 } 25 used[i] = true; 26 path.add(nums[i]); 27 permuteHelper(nums); 28 path.removeLast(); 29 used[i] = false; 30 } 31 } 32 }
1 // 解法2:通过判断path中是否存在数字,排除已经选择的数字 2 class Solution { 3 List<List<Integer>> result = new ArrayList<>(); 4 LinkedList<Integer> path = new LinkedList<>(); 5 public List<List<Integer>> permute(int[] nums) { 6 if (nums.length == 0) return result; 7 backtrack(nums, path); 8 return result; 9 } 10 public void backtrack(int[] nums, LinkedList<Integer> path) { 11 if (path.size() == nums.length) { 12 result.add(new ArrayList<>(path)); 13 } 14 for (int i =0; i < nums.length; i++) { 15 // 如果path中已有,则跳过 16 if (path.contains(nums[i])) { 17 continue; 18 } 19 path.add(nums[i]); 20 backtrack(nums, path); 21 path.removeLast(); 22 } 23 } 24 }
47.全排列 II
题目链接:46. 全排列 - 力扣(LeetCode)
思路
这道题与上面一题大致,区别在与给定一个可包含重复数字的序列,要返回所有不重复的全排列。
因此这道题需要用到 “树层去重” 和 “树枝去重” 。
详细过程如下

注意:这里去重的时候一定要先对元素进行排序,这样我们才方便通过相邻的节点来判断是否重复使用了。
代码
1 class Solution { 2 //存放结果 3 List<List<Integer>> result = new ArrayList<>(); 4 //暂存结果 5 List<Integer> path = new ArrayList<>(); 6 7 public List<List<Integer>> permuteUnique(int[] nums) { 8 boolean[] used = new boolean[nums.length]; 9 Arrays.fill(used, false); 10 Arrays.sort(nums); 11 backTrack(nums, used); 12 return result; 13 } 14 15 private void backTrack(int[] nums, boolean[] used) { 16 if (path.size() == nums.length) { 17 result.add(new ArrayList<>(path)); 18 return; 19 } 20 for (int i = 0; i < nums.length; i++) { 21 // used[i - 1] == true,说明同⼀树⽀nums[i - 1]使⽤过 22 // used[i - 1] == false,说明同⼀树层nums[i - 1]使⽤过 23 // 如果同⼀树层nums[i - 1]使⽤过则直接跳过 24 if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) { 25 continue; 26 } 27 //如果同⼀树⽀nums[i]没使⽤过开始处理 28 if (used[i] == false) { 29 used[i] = true;//标记同⼀树⽀nums[i]使⽤过,防止同一树枝重复使用 30 path.add(nums[i]); 31 backTrack(nums, used); 32 path.remove(path.size() - 1);//回溯,说明同⼀树层nums[i]使⽤过,防止下一树层重复 33 used[i] = false;//回溯 34 } 35 } 36 } 37 }
扩展
这道题去重的关键在于
1 if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) { 2 continue; 3 }
如果改成 used[i - 1] == true, 也是正确的!,去重代码如下:
1 if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == true) { 2 continue; 3 }
第一个是对树层进行去重,而第二个代码是对树枝去重。
对于排列问题,树层去重 与 树枝去重 都是可行,区别在于 树层的效率会比较高一点。
详细差别如图:
树层去重

树枝去重


浙公网安备 33010602011771号