最近算法题也刷了不少,小有感悟。
我觉得刷题时一般的思维方式是迭代思维。就是我们老是想着通过循环,通过顺序解决下一个来迭代解决整个问题。
典型事例有: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; }
算法问题多变,复杂。如果迭代不行,不妨试试递归方式。“一子动天下”是一种较好的讨论方式。