Subsets & Combinations & Combination Sum

Backtracking 不变的经典啊。

唯一注意的地方是,start == nums.length 的时候其实也是要添加结果的,不然会错。写回溯的时候,base case 要想清楚。

public class Solution {
    public List<List<Integer>> subsets(int[] nums) {
        List<List<Integer>> rst = new ArrayList<>();

        dfs(rst, new ArrayList<>(), nums, 0);

        return rst;
    }

    private void dfs(List<List<Integer>> rst, List<Integer> list, int[] nums, int start){
        //if(start > nums.length) return;

        rst.add(new ArrayList<Integer>(list));

        for(int i = start; i < nums.length; i++){
            list.add(nums[i]);

            dfs(rst, list, nums, i + 1);

            list.remove(list.size() - 1);
        }
    }
}

有重复元素也不难,先 sort,然后每一步上注意同样的元素别重复加一遍就好了。

这类题的搜索树都是极度向左倾斜的结构,节点数为 2^n;

为什么觉得我好像在这 gitbook 里写过这题。。

  • 元素可以重复选用,传进去的 index 可以以 i 为准。

  • 但是还是不能走回头路,不然会有重复答案。

  • 所有元素为正整数非常重要,要么这题可能有无数个解。

  • 没重复元素,想避免重复解,用 index 单调向前;

  • 有重复元素,想避免重复解,index 之外每轮 dfs 末尾直接把指针移到下一个新元素上,一个元素在一轮 for loop 里只加一次。

Trivial problem.

题目换成这样了之后依然是一个搜索问题,但是有了新的有趣特性。

主要在于这题和之前的 combination sum 还不太一样,它把 [1,3] 和 [3,1] 这种重复搜索算成两个解。于是强行制造了 overlap subproblems.

于是这题用搜索解会 TLE,加个 hashmap 做记忆化搜索,立刻就过了。

24ms ,时间复杂度较高,不如下面那个迭代的写法速度快。

O(n^d),d代表得到 >= target 的搜索深度。

论坛里还有比较有趣的迭代写法,只需要 3ms,原理和 climbing stairs 差不多,建一个大小等于 target + 1 的 array 代表多少种不同的方式跳上来,依次遍历即可。

时间复杂度 O(n * target) + O(n log n)

原理和 climbing stairs 差不多,建一个大小等于 target + 1 的 array 代表多少种不同的方式跳上来,依次遍历即可。

时间复杂度 O(n * target)

注意这里内外循环的顺序不能错,要先按 sum 从小到大的顺序看,然后才是遍历每个元素。因为所谓 bottom-up,是相对于 sum 而言的,不是相对于已有元素的 index 而言的。

可以看到,在只取 min/max 的时候我们不同的循环顺序都能保证正确答案;但是求解的数量时,要以 target value 为外循环。

Last updated