最近算法题也刷了不少,小有感悟。

我觉得刷题时一般的思维方式是迭代思维。就是我们老是想着通过循环,通过顺序解决下一个来迭代解决整个问题。

典型事例有:2Sum, 3Sum, 排序问题,以及用双指针或快慢指针法解决的问题。

迭代思维是一种很直接的思维方式,但绝不简单,因为找到正确的循环方式并不是一件容易的事情。

但有些问题用迭代思维方式是很难解决的,或者说这些问题本身就不适合用循环来解。

比如求组合数问题。C(n, 2)还能用2层循环来解,但C(n, m)呢?用迭代就很难求解了,不自然。这是用递归思维方式却很自然。

先取一个,把问题化为C(n, m-1), 再取一个,把问题化为C(n, m-2), 如此递归即可。

    public static int combinationNum = 0;
    public static void combine(int[] target, int[] result, int st, int index,int m){
        if (m == 0){
            combinationNum++;
            for (int i : result){
                System.out.printf(i + " ");
            }
            System.out.println();
            System.out.println(combinationNum);
        }else {
            for (int i = index; i < target.length - m + 1; i++) {
                result[st] = target[i];
                combine(target, result, st+1, i+1,m-1);
            }
        }
    }

  求排列数也是如此,把A(n,n) 化为A(n-1,n-1),直至化为A(1,1)。

    public static void swap(int[] result, int st, int ele){
        int tmp = result[st];
        result[st] = result[ele];
        result[ele] = tmp;
    }

    //轮番把各个元素放在第一位,然后递归求解。注意复位。
    public static void arrange(int[] result, int st){
        if (st == result.length-1){
            for (int i : result){
                System.out.printf(i + " ");
            }
            System.out.println();
        }else {
            for (int i = st; i < result.length; i++) {
                swap(result, i, st);
                arrange(result, st+1);
                swap(result,i , st);
            }
        }
    }

  对于这类型的问题用递归的感觉就是干净,简洁,有一种逻辑的美感。

不过把问题递归化并不是一件容易的事。有一种常用的技巧是“一子动天下”。就是针对一个元素的有无进行分类讨论,这个元素常常是最后一个。这时往往可以把问题二分递归化。

上面两个例子实际上也是针对一个元素进行讨论,不过它们是把问题多分化,所以外层有循环来遍历。下面给出一个二分递归化的例子。

给出一个数组,里面是不重复的int 数字。 求满足和为S的所有数字组合。(数组[2, 3, 6, 7],  和为 7, 结果为:[ [7], [2, 2, 3] ] )。也就是换硬币问题。

这时我们可以针对最后一个元素是否包含,把问题二分化。然后递归遍历整个解法空间:

    private List<List<Integer>> result = new ArrayList<>();

    //一子动天下的典型事例,递归思维方式的典型。dfs遍历整个解法空间,自然得出结果。干净,简洁。
    public void combinations(int[] candidates, int target, List<Integer> combination, int limit){
        if (limit < 0 ){
            return;
        }else if (target == 0){
            result.add(combination);
        }else {
            if (target - candidates[limit] >= 0){
                combination.add(candidates[limit]);
                combinations(candidates, target - candidates[limit], new ArrayList<>(combination), limit);
                combination.remove(combination.size()-1);
            }
            combinations(candidates, target, combination, limit-1);
        }
    }

    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        Arrays.sort(candidates);
        int limit = candidates.length-1;
        combinations(candidates, target, new ArrayList<Integer>(), limit);
        return result;
    }

  

算法问题多变,复杂。如果迭代不行,不妨试试递归方式。“一子动天下”是一种较好的讨论方式。