回溯(子集)

73 阅读2分钟

子集

题目:78

  • 只不过比组合问题多了一个组合长度
  • 毕竟子集啦,不同长度啦
  • 再用一个for循环表示组合长度就行
  • 递归返回值和参数:这个len就表示长度

private void backTracking(int[] nums, int startIndex, int len)

  • 结束条件:长度达到或者一些超出边界
        if (path.size() == len) {
            res.add(new ArrayList<>(path));
            return;
        }
        if (startIndex >= nums.length || path.size() > len) {
            return;
        }
  • 主方法里这样就行啦
        res.add(path);//表示最开始的空集
        for (int i = 1; i <= nums.length; i++) {
            backTracking(nums, 0, i);//表示每一次循环都是不同的数组长度
        }

子集II

题目:90

  • 依然是关于去重的问题
  • 涉及到去重,最好先给数组排序,这样方便去重,即跳过一段重复数字后,后面再无该数字
  • 这里的去重就和之前的(40:数组总和II)一个一样,要么用used[i] 要么用索引跳过
  • 这里说一下used数组的逻辑:先跳到重复数字段的最后一个重复数字,比如11111,利用该条件直接跳到11111(最后一个1),然后用这最后一个1。
  • 这里的最后一个1,指的是当前层使用过的相同的元素,第一个出现的1在当前层已经用过了,要不然不会有nums[i] == nums[i - 1] && !used[i - 1]的判定条件......应该,毕竟说明used[i-1]已经给用过然后是回溯后的样子了?
  • 至于会出现的数组[1,1],两个1是在不同层取到的啦
            if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]){
                continue;
            }
            
            path.add(nums[i]);
            used[i] = true;
            subsetsWithDupHelper(nums, i + 1);
            path.removeLast();
            used[i] = false;
  • 另一个用索引跳过的也是这个意思啦
    if ( i > start && nums[i - 1] == nums[i] ) {
        continue;//这里跳过的是start啦,说明传来的start已经用过哩
      }

递增子序列

题目:491

  • 要是递增,看题目,数组里的顺序还不能变,而且会把两个相同的也认识
  • 毕竟就是求自增子序列,所以原来的顺序不能变,故去重方法不能照用以前的了
  • 方法返回值和参数,因为毕竟是不能重复的,所以还是要有startIndex
  • 返回条件:题中说了,大于2就行,不过此时不能返回,因为还要继续把树枝延伸下去,取到这个树枝上的所有节点。如果此时返回了,就都是长度为2的答案了
        if (path.size() > 1) {
            res.add(new ArrayList<>(path));
        }
  • 单层递归逻辑:同一父节点下的同层上使用过的元素就不能再使用了 所以used[]数组在回溯方法里创建,代表每一层的元素使用情况,又因为used[]只负责本层,所以不用再把它弄到false。然后还要判定是不是递增的情况
       
        
        int[] used = new int[201];
        for (int i = start; i < nums.length; i++) {
            if (!path.isEmpty() && nums[i] < path.get(path.size() - 1) ||
                    (used[nums[i] + 100] == 1)) continue;
                    
            // nums[i] < path.get(path.size() - 1) :当前nums[i]小于单位列表的最后一个,不是递增,跳过
            //(used[nums[i] + 100] == 1) :当前数值已经用过了
            used[nums[i] + 100] = 1;
            path.add(nums[i]);
            backtracking(nums, i + 1);
            path.remove(path.size() - 1);
        }
  • 为什么used[]不是代表该索引对应的值有没有用到(int[] used = new int[nums.length];),而是代表数值有没有用到(int[] used = new int[201];)
  • 因为给的数组不一定是排序的,如果不是排序的,那么利用索引去重没有意义,毕竟两个一样的数字可能是不连续的,如果用索引去重的话,会出现下面的重复情况

[[4,6],[4,6,7],[4,6,7,7],[4,6,7],[4,7],[4,7,7],[4,7],[6,7],[6,7,7],[6,7],[7,7]]

  • 总之就是同层的不能用同一个元素,但是下一层可以和上一层用一样的元素。之前的回溯也是这样,不要因为单个集合里允许有重复数字就弄混,要分清这一层和下一层,即广度和深度的不同