题目描述
和为S的连续正数序列:输入一个正数S,打印所有和为S的连续正数序列(至少含有两个数)
例如,输入15,有1+2+3+4+5和4+5+6和7+8三种情况,所以打印三个序列。
思路:如果做过57.1题,和为S的两个数字这道题,就可以借鉴做法。即利用两个指针记录首尾,然后进行相关操作。本题使用一前一后两个指针,两个指针范围内的数字之和cur和给定的sum进行判断。
过程:以15为例,假如我们找到了{4,5,6},需要找下一个解,其实我们可以有3个选择,移动first,移动last,或者两个指针都移动。书中选择移动last.这里我选择的是两个指针都移动,并且同步更新当前的范围之和cur。如果cur与sum相同,那不用说,直接往ArrayList里装,如果当前移动后的cur超过了sum,说明要减去某个数,则first后移,同步更新cur,如果移动后的cur小于sum,则说明要加上某个数,则last后移。移动指针并更新cur后,再次进入while,判断是否相同。
细节:first超过sum的一半时,就说明没有这样的序列了,就可以结束了,而不用一直让first++.
写法1:
根据offer书上的写法,汲取了一些优点,改成了自己的版本,和很多人写法不同的是,我在修改指针的同时,就更新了和,而不用再调用一个求和函数。
import java.util.ArrayList; public class Solution { public ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) { ArrayList<ArrayList<Integer>> result = new ArrayList<ArrayList<Integer>>(); if(sum < 3)//至少为3,分成1和2 return result; int first = 1; int mid = (1+sum)/2;//边界条件 int last = 2; int cur = first + last;//cur是当前的和,初始为3 while(first < mid){ ArrayList<Integer> list1 = new ArrayList<>(); if(cur == sum){//这里碰到了相同 for(int i = first; i <= last; i++){ list1.add(i); } result.add(list1);//序列装到list,然后list再成为result的一个元素 first++;//同时维护指针 last++; cur = cur+last-first+1;//更新和 } else if(cur > sum && first < mid){//大于的话,也进行更新,然后进入while cur = cur - first; first++; } else{//小于的时候,更新指针,更新和,然后进入while last++; cur += last; } } return result; } }
写法2:剑指offer书上改成java版本
这里对书上的写法进行解释。如果等于,直接装给ArrayList。那么下一次移动的时候,只移动last角标。(这样做也有一点好处)。移动last后,就会出现cur大于sum时,移动first,再次移动first时就可能出现相同,这里可以判断也可以不判断(书中选择在这里判断了,所以看到这里又有一个判断并装给ArrayList的动作),在不断移动first中,又可能出现cur小于sum(移动过头了,好好理解这个过程!)。小于后,就再移动last,并更新cur,就进入下一次while了。
可以看到,书上将大于,小于,等于三种情况,分成了两次移动last,一次移动first,所以在两次移动last的部分,进行了代码复用。(last++只出现了一次,这就是我刚刚说的好处^_^)
而我的是分成了三种,相同更新两个指针,大于时更新first,小于时更新last。不仅如此,书上小于这种情况时,还一步判断了是否有解的情况(又使用了while,while里又套了个while)。而我是将所有的情况分析都留个同一个while判断.
import java.util.ArrayList; public class Solution { public ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) { ArrayList<ArrayList<Integer>> result = new ArrayList<ArrayList<Integer>>(); if(sum < 3) //最低也是1和2,所以sum至少为3. return result; int first = 1; int mid = (1+sum)/2;//标记结束的标志 first=mid说明无解 int last = 2; int cur = first + last;//cur为当前的两个指针之间所有数的和,初始值就是3 while(first < mid){ ArrayList<Integer> list1 = new ArrayList<>();//为了避免重复添加,必须先new if(cur == sum){//和相等,则用一个list记录当前整个序列 for(int i = first; i <= last; i++){ list1.add(i); } result.add(list1);//再倒一手给result。 } //如果不相等,就可能大于或者小于。 while(cur > sum && first < mid){//如果大于了,并且first没超标,说明还能移动 ArrayList<Integer> list2 = new ArrayList<>(); cur = cur - first;//first角标移动时,要从cur中减去这个数 first++;//这里采用的是,先减,再动角标。先动角标就应该写成cur=cur-first+1; if(cur == sum){//一旦移动后,就可能相等,书中选择在这里再次判断,而我不是。 for(int i = first; i <= last; i++){ list2.add(i); } result.add(list2); } } //此时cur<sum,就应该让last++,然后更新cur //下面这两句代码进行了复用,等于以及小于的情况,都是这个时候才维护指针。 last++; cur += last; } return result; } }
总结:offer书上的写法不太容易第一时间理解。但是这个思想肯定没有问题,根据这个思想形成自己的解题思路,正常写就可以写出来。