LeetCode Combination Sum 和 Combination Sum II 解题心得

来源:互联网 发布:斐波那契数列java 编辑:程序博客网 时间:2024/05/21 10:49

Combination Sum 原题如下:

Given a set of candidate numbers (C) and a target number (T), find all unique combinations in C where the candidate numbers sums to T

The same repeated number may be chosen from C unlimited number of times.

Note:

  • All numbers (including target) will be positive integers.
  • Elements in a combination (a1a2, … , ak) must be in non-descending order. (ie, a1 ≤ a2 ≤ … ≤ ak).
  • The solution set must not contain duplicate combinations.

For example, given candidate set 2,3,6,7 and target 7
A solution set is: 
[7] 
[2, 2, 3] 

这道题跟Palindrome Partitioning做法类似,都是用到了Recursion(递归)的思想。 因为可以使用重复的数组元素(Palindrome Partitioning里面为相邻元素,不考虑重复与否,只考虑回文情况),构建形为rec(int[] candidates, int target, int index, ArrayList<ArrayList<Integer>> result, ArrayList<Integer> list)的递归函数便可解决。当然解决这个问题,第一步要做的是对原数组排序,这里使用的是Arrays.sort(candidates); 这样做的目的自然是为了满足题目中第二个条件,即:最终给出组合的所有元素必须是以非递减形式排列的。下面最重要的一步则是构建这个递归函数,我们这个函数有5个变量,重点说下第二个和第三个变量。第二个变量target是逐渐变小的一个值,最初为要利用给定数组中的元素凑成的那个值,最终是什么呢?这里就要考虑我们构建的递归函数的停止条件了,显而易见当target值小于0的时候说明当前加入到一维ArrayList中的值过大,这种情况我们需要停止这个递归函数。另外一种情况便是target恰好为0的时候,这说明了我们已经凑成了要找的值,当前一维ArrayList中的所有元素的和为target,我们找到了一种组合。这样的话,我们就可以将此一维ArrayList加入到最终的二维ArrayList,即result中了。这里有一点需要特别特别注意,千万不能使用result.add(list)添加这个一维ArrayList, 正确的做法是result.add(new ArrayList<Integer>(list))这里先做的是对list的新建拷贝,然后将其添加到结果result中。如果直接添加的话,最终result里面的一维list会被下面for loop里的代码改变,或者说以后任何对于list的改变都将影响到已经添加到result中的list,因为我们只是将list的引用放在了result中,自然会有以上不好的影响。这一部分算是java里比较需要注意的地方,相关文章链接后续补上。

我们有了stop rules,那么问题来了:正常情况下改如何处理呢?总体思路比较简单,下面的代码应该足够清晰明了,这里只说一点,if(i > 0 && candidates[i] == candidates[i-1]) continue;这个判断语句是很关键的一句,相同的问题体现在Combination Sum II里。可以思考一下为什么要用这个语句进行判断,为什么当前元素与左边相邻元素相同便跳过?这里的话不得不提一下下面递归函数的写法

rec(candidates, target-candidates[i], i, result, list);
注意第三个参数为i,这是很重要的一点,i就意味着函数先会在当前元素位置递归下去,因为i不会改变。这样做的话相当于重复地测试堆砌当前元素是不是可以凑够target值,这样一来,为了避免重复计算,我们自然可以将所有后面遇到的当前元素与左邻元素相等的情况跳过。这也就解释了上面那个if语句。这句话同样是Combination Sum II的值得思考的点。这道题到此已经相对明了了,我们move on到进阶版。

public class Solution {    public ArrayList<ArrayList<Integer>> combinationSum(int[] candidates, int target) {        if(candidates == null || candidates.length < 1) return null;                ArrayList<ArrayList<Integer>> result = new ArrayList<ArrayList<Integer>>();        ArrayList<Integer> list = new ArrayList<Integer>();                Arrays.sort(candidates);        rec(candidates, target, 0, result, list);                return result;    }        public void rec(int[] candidates, int target, int index, ArrayList<ArrayList<Integer>> result, ArrayList<Integer> list){        if(target < 0) return;        else if(target == 0){            result.add(new ArrayList<Integer>(list));            return;        }                for(int i = index; i < candidates.length; i++){            if(i > 0 && candidates[i] == candidates[i-1]) continue;            list.add(candidates[i]);            rec(candidates, target-candidates[i], i, result, list);            list.remove(list.size() - 1);        }    }   }


Combination Sum II 原题如下:

Given a collection of candidate numbers (C) and a target number (T), find all unique combinations in C where the candidate numbers sums to T.

Each number in C may only be used once in the combination.

Note:

  • All numbers (including target) will be positive integers.
  • Elements in a combination (a1a2, … , ak) must be in non-descending order. (ie, a1 ≤ a2 ≤ … ≤ ak).
  • The solution set must not contain duplicate combinations.

For example, given candidate set 10,1,2,7,6,1,5 and target 8
A solution set is: 
[1, 7] 
[1, 2, 5] 
[2, 6] 
[1, 1, 6] 

进阶版与普通版的不同之处在于不能无限使用同一个元素,而是只能在给定数组中取不同的位置的元素。但是位于不同位置的重复元素将可以被用来拼凑想要的target值。这样的话我们其实还是可以对数组先进行排序的,只不过后续处理与普通版稍有不同罢了。大体思路一致,这里只对2个点做下强调,第一个便是if语句,

if(i > index && num[i] == num[i-1]) continue;
这里的if语句的第二个条件没变而第一个条件看起来有些怪异,这个改动有什么作用么?举个例子,
num=[1, 1], target=1
如果没有这个if语句,程序会输出为
[[1], [1]]
这就违背了第三个条件,即所有组合不能重复。这就是此if语句的价值所在,当list中元素的和加上某一个数即凑成要找的target值时,而恰好这个数有重复出现,这个判断则显得尤为重要,因为最左边位置的数即满足条件,我们不需要再去考虑后面重复的元素。可以说这是这个进阶版的要求的体现,其余部分基本同普通版一致。

而另外一个重要的点则是下面这段代码:

rec(num, target-num[i], i+1, list, result);

这里的改变也就是第三个参数由i变为了i+1,经过上面if语句的解释后以及结合题目要求不能重复使用同一元素,这个问题则变得很简单了。想明白了这2个点,进阶版也就迎刃而解了。虽然本意是为了加深对题目的理解和吃透同类题目,但也还是希望这些讲解对于看到此文章的人有些许帮助。


public class Solution {    public ArrayList<ArrayList<Integer>> combinationSum2(int[] num, int target) {        if(num == null || num.length == 0) return null;                ArrayList<ArrayList<Integer>> result = new ArrayList<ArrayList<Integer>>();        ArrayList<Integer> list = new ArrayList<Integer>();        Arrays.sort(num);                rec(num, target, 0, list, result);                return result;    }        public void rec(int[] num, int target, int index, ArrayList<Integer> list, ArrayList<ArrayList<Integer>> result){        if(target < 0) return;                if(target == 0){            result.add(new ArrayList<Integer>(list));            return;        }                for(int i=index; i<num.length; i++){            if(i > index && num[i] == num[i-1]) continue;            list.add(num[i]);            rec(num, target-num[i], i+1, list, result);            list.remove(list.size()-1);        }    }}


0 0
原创粉丝点击