Combination Sum-leetcode No.39里引发的一些忽略点
leetcode上有这么一道题:
Given a set of candidate numbers (C) (without duplicates) 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.
- 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] ]
大概意思就是给定一个int型数组candidates,和一个int型target,找到在数组里所有满足和为target的数组,注意:所有数均为正整数,candidates里的元素可以重复无限次,且组中结果里没有重复结果。
比如:candidates数组是:[2,3,6,7],target是7,那么所有满足要求的结果是:
[
[7],
[2,2,3]
]
这里面2就重复了。
【思路】
如果是刷题不多的同学,或者算法直觉不太好的同学,这种题一眼看起来似乎很难,可能性太多了,不能一一枚举吧。但这篇文章重点不讨论这个,只提一下思路就行,主要是讨论一下我的做法里面遇到的一些容易疏忽的小问题。
这种题思路如下:
这种结果再怎么复杂,基本也是枚举出来的,但是不能太暴力枚举。所以大概直觉是回溯,且打算采用递归。
以例子为例,大致原理如下:
首先将数组排序,从小到大,由于数组没有重复元素,在找解集时不用考虑重复元素的情况。
解集就在这些数形成的树中。树的每个下一级的元素,总是当前元素及其后面的数字(因为前面的数字没有意义,已经在之前处理过了,否则就重复了;依然从当前数开始,是因为允许重复),
且总要和sum相加,如果和依旧小于等于target,才会向下延伸,否则不加入树枝。

主要意思就是,根据和(sum)的情况,以及结合目前的和加上即将要相加的数字,如果这部分不超过target,则表示这一分支存在有解的可能性,就继续“深入”进去查找。否则:
>如果目前和小于target,且加上即将相加的数字和大于target,则舍弃目前最后一个数字,比如上图中最左边的解,当某个分支解为[2,2,2]时,3个2和为6了,如果再加上最小的2,已经超过了target=7,故没必要继续下去,且将这个2弹出(栈里为pop),剩下的解分支为:[2,2],转向2的下一个数字:3;
>如果目前和小于target,但是加上即将相加的数3,则可以进入下一步,正式加上3,和正好为target=7,则记录这个解,将其加入到解集中。此时这个支路已经找到解了,没必要再继续下去(因为继续下去和肯定大于target)。这里要注意一下:找到解时,要将记录解的栈中的最后一个数弹出,即3。
由于采用递归,每当加入一个数字,处理完其后续的解后,必须将此数字弹出,以便返回上一层,让上一层继续尝试这个数字之后的其他数字。
这篇笔记主要想提一个我忽略的问题,所以第一次写答案时的代码如下:
1 public class Solution { 2 List<List<Integer>> res=new ArrayList<List<Integer>>(); 3 LinkedList<Integer> al=new LinkedList<Integer>(); 4 int tSum; 5 public List<List<Integer>> combinationSum(int[] candidates, int target) { 6 int len=candidates.length; 7 if(len==0) 8 return res; 9 Arrays.sort(candidates); 10 getCombinationSum(0,target,candidates); 11 return res; 12 } 13 public void getCombinationSum(int left,int target,int[]nums){ 14 if(tSum>=target){ 15 return; 16 } 17 int len=nums.length; 18 while(left<len&&nums[left]<=target&&tSum+nums[left]<=target){ 19 tSum+=nums[left]; 20 al.push(nums[left]); 21 if(tSum==target){ 22 res.add(al); 23 if(!al.isEmpty()){ 24 tSum-=al.peek(); 25 al.pop(); 26 } 27 return; 28 } 29 getCombinationSum(left,target,nums); 30 if(!al.isEmpty()){ 31 tSum-=al.peek(); 32 int t=al.pop(); 33 } 34 left++; 35 } 36 } 37 }
但是最后答案个数是对的,但是每个解都是空的,比如:[[],[]]这种。
刚开始搞不明白哪里出错了,后来一想,将代码改成如下:
1 public class Solution { 2 List<List<Integer>> res=new ArrayList<List<Integer>>(); 3 LinkedList<Integer> al=new LinkedList<Integer>(); 4 int tSum; 5 public List<List<Integer>> combinationSum(int[] candidates, int target) { 6 int len=candidates.length; 7 if(len==0) 8 return res; 9 Arrays.sort(candidates); 10 getCombinationSum(0,target,candidates); 11 return res; 12 } 13 public void getCombinationSum(int left,int target,int[]nums){ 14 if(tSum>=target){ 15 return; 16 } 17 int len=nums.length; 18 while(left<len&&nums[left]<=target&&tSum+nums[left]<=target){ 19 tSum+=nums[left]; 20 al.push(nums[left]); 21 if(tSum==target){ 22 LinkedList<Integer> tal=new LinkedList<Integer>(al); 23 res.add(tal); 24 if(!al.isEmpty()){ 25 tSum-=al.peek(); 26 al.pop(); 27 } 28 return; 29 } 30 getCombinationSum(left,target,nums); 31 if(!al.isEmpty()){ 32 tSum-=al.peek(); 33 int t=al.pop(); 34 } 35 left++; 36 } 37 } 38 }
可以发现,第22行左右这里不一样。因为这里al是全局的,且解集中每个解都是对al的引用,故最终处理完成时,al也弹完时,是空的,所以最后解集全是空的解,但是个数是对的。
基础不扎实,就会这么蠢-_-|||

浙公网安备 33010602011771号