Subsets, Combination 与 Permutation

Combination 类问题最重要的是去重, dfs() 函数里带一个 index 参数可以很好的解决这个问题。

顺序问题中有“单序列”和“全序列”顺序,分别对应一个序列中元素的顺序和整个序列中子序列顺序。可以通过子序列翻转或者全局翻转操作,利用两次翻转相互抵消的特点解决序列顺序问题。

用数学语言描述就是: ' 代表 inverse

S = ABCD

S' = D'C'B'A'

(A'B'C'D')' = DCBA

Combination 类问题是典型的搜索问题,除了 DFS + backtracking 之外,combination 里最重要的就是“去重”,怎么让自己的搜索树不回头地往前走。

在这个问题里,k = depth of tree,n = branching factor. 当然因为解个数的唯一性,不是每个节点的 fan out 都一样。

在这个具体问题里,因为解是单调连续增加的序列 1,2..n,去重方法上可以稍微取巧一些:dfs里增加一个“前一个元素”的参数,每一层递归只考虑比上一个元素大的值。

public class Solution {
    /**
     * @param n: Given the range of numbers
     * @param k: Given the numbers of combinations
     * @return: All the combinations of k numbers out of 1..n
     */
    public List<List<Integer>> combine(int n, int k) {
        // write your code here
        List<List<Integer>> rst = new ArrayList<List<Integer>>();
        dfs(rst, new ArrayList<Integer>(), n, k, 0);
        return rst;
    }

    private void dfs(List<List<Integer>> rst, List<Integer> list, int n, int k, int prev){
        if(list.size() == k) {
            rst.add(new ArrayList<Integer>(list));
            //list.remove(list.size() - 1);
        }

        for(int i = prev + 1; i <= n; i++){
            if(list.contains(i)) continue;
            list.add(i);
            dfs(rst, list, n, k, i);
            list.remove(list.size() - 1);
        }
    }
}

相似的题有一样的思路,不同的题有不同的坑。

为了去重的考虑,还是要 dfs 参数里带 index. 这里有一个细微的差别,因为同一个数可以用多次,新一层 dfs 迭代的时候要从上一层一样的 index 开始。然而还是要注意不要去看 index 之前的元素。

同时因为同一个元素可以用多次,必须要有一个有效的 dfs 终止条件,不然搜索树会永远一直加下去。。考虑到给定条件是“所有元素都为正整数”,我们就可以在当前 sum > target 的时候终止搜索。如果可以重复元素又有负数的话,这题就没法做了。

OJ 要求每一个结果都是 sorted list,稍微有点二逼,注意不能直接 sort 函数里的 list,因为会打乱其他 dfs 中的结果 (same memory address pass by value),要去新建一个 list copy 再调用 Collections.sort()

都是简单套路和小变形。这次每轮dfs都考虑所有元素(所以不用传 index 参数了),传个 boolean array 只挑没用过的数字就可以了。

基本没啥区别。只加了新的一行,确保下在一个 dfs 返回,回溯结束之后,下一个数别选一样的就行了。

Last updated