剑指offer解题报告(Java版)——和为s的两个数,一串连续数 41

   

引言

   

第一问题很简单,leetcode上也有相应的题,2Sum问题,leetcode上还有2Sum的进阶版3Sum,只是在这个问题上增加了一层循环而已,另外还有3Sum_Close问题,就是和接近s的三个数,本文将依次介绍2Sum问题,3Sum问题,以及3Sum_close,以及一串连续数问题

   

对于3Sum的问题,本文除了常用的退化为2Sumn平方的解法外,还提供了一种hash的方法,使得利用hash的方法使得4Sum问题不再是n三次方的时间复杂度,可以降到n平方的时间复杂度

   

2Sum问题

   

这里需要注意的就是要用两个指针来减少对数组的遍历,不然用常规的方法就要遍历两遍数组,两层循环,n平方的时间复杂度

   

用一个pointHead从前往后走,用一个pointEnd从后往前走,两个如果没遇到就一直这么走下去,判断这两个数加起来是否满足条件,如果满足,输出这两个数,如果不满足,看和是大于还是小于,大于,说明和需要减小才行,于是pointEnd往前走,如果小于,说明和需要被增大才行,于是pointHead往后走

   

public void findNumbersWithSum(int[] sortedArray, int number)

{

if(sortedArray==null)

return ;

int pointHead=0;

int pointEnd=sortedArray.length-1;

while(pointEnd>pointHead)

{

long curSum=sortedArray[pointEnd]+sortedArray[pointHead];

if(curSum==number)

{

System.out.println(sortedArray[pointHead]);

System.out.println(sortedArray[pointEnd]);

break;

}

else

{

if(curSum>number)

pointEnd--;

else

pointHead++;

}

}

}

   

以上代码有个问题就是找到一个结果就break了,如果希望找到所有的呢,那么将break变为pointHead++pointEnd--继续走下去即可

   

就算改了还是有一个问题,就是结果会不会有重复的情况呢,答案是有的,比如说

int[] array={1,2,4,7,7,8,8,11,15};

   

那么如果解决重复的问题,一个简单的不增加循环的方法就是在每次循环体的开始检查一下pointHead的那个值是否和pointHead-1的那个值相等,如果相等,则pointHead++并且continue,同样的适合pointEnd

   

if (pointEnd<sortedArray.length-1&&sortedArray[pointEnd]==sortedArray[pointEnd+1]) {

pointEnd--;

continue;

}

if (pointHead>0&&sortedArray[pointHead]==sortedArray[pointHead-1]) {

pointHead++;

continue;

}

   

3Sum问题

   

外加一层循环,遍历数组所有数,这个数记为first,那么问题转换为在之后的数中找两个树second以及third,使得first+second+third=target结束循环,在first固定的每层循环中如果小就second+,如果大就third-

   

同样需要考虑的一个问题是找到的结果是否会出现重复的情况,除了上面说到的那种方法之外,还有另外一种方法就是用一个hashmap中去重

   

将满足条件的结果(三个数字)放入midresult中,midresult是个链表,将midresult放入hashmap中去重

再将hashmap中取出来放入resultresult也是个链表,相当于最终的结果是个链表,每个节点是一个解,每个解是一个链表,这个链表中有三个数

   

public static ArrayList threeSum(int[] num) {

Arrays.sort(num);

ArrayList result = new ArrayList();

Map hm = new HashMap();

   

for (int firstPos = 0; firstPos < num.length; firstPos++) {

int secPos = firstPos + 1;

int thirdPos = num.length - 1;

while (secPos < thirdPos) {

if (num[firstPos] + num[secPos] + num[thirdPos] == 0) {

ArrayList<Integer> midResult = new ArrayList<Integer>();

midResult.add(num[firstPos]);

midResult.add(num[secPos]);

midResult.add(num[thirdPos]);

hm.put(midResult, false);

secPos += 1;

thirdPos -= 1;

} else if (num[firstPos] + num[secPos] + num[thirdPos] < 0) {

secPos += 1;

} else {

thirdPos -= 1;

}

}

}

Iterator it = hm.entrySet().iterator();

while (it.hasNext()) {

//                        Entry entry =(Entry) it.next();

//                        result.add(entry.getKey());

result.add(it.next());

}

return result;

   

}

   

2Sum3Sum的时间复杂度分析

   

我们可以很轻易的就知道2sum的算法复杂度是O(NlogN),因为排序用了NlogN,头尾指针的搜索是线性的,所以总体是O(NlogN)

   

考虑3sum, 3sum的算法复杂度就是O(N^2), 注意这里复杂度是N平方,而不是O(N^2 log N),很容易在这里犯错误

   

仔细想想可以知道因为你排序只需要排一次,后面的工作都是取出一个数字,然后找剩下的两个数字,找两个数字是2sum用头尾指针线性扫。

   

推广下去4sum也就可以退化成3sum问题,那么以此类推,K-sum一步一步退化,最后也就是解决一个2sum的问题,K sum的复杂度是O(n^(K-1))

   

3Sum_close问题

   

close问题需要维护一个距离dis,也就是得到的和与真实想要的和之间的误差,如果新的比旧的小,则更新结果,另外还要维护一个真实的和ret

   

int dis = Integer.MAX_VALUE;

int ret = 0;

int sum = num[i] + num[j] + num[k];

int minus = sum - target;

int d = Math.abs(minus);

if (d < dis) {

dis = d;

ret = sum;

}

if (minus == 0)

return target;

if (minus < 0) {

j++;

} else {

k--;

}

   

算法提升

   

这里的算法提升主要是用到hash,用hash的话check某个值存在不存在就是常数时间,那么2sum的解法可以是线性的

   

比如用hashmap,给定一个sum, 只要线性扫描, 对每一个number判断sum – num存在不存在就可以了。

   

注意这个算法对有重复元素的序列也是适用的。比如 2 3 3 4 那么hashmap可以使 hash(2) = 1; hash(3) = 1, hash(4) =1其他都是0, 那么check的时候,扫到两次3都是check sum-3在不在hashmap中,注意最后返回所有符合的pair的时候也还是要去重。

   

这样推广的话 3sum 其实也有O(N^2)的类似hash算法,这点和之前是没有提高的,但是4sum就会有更快的一个算法。

   

4sumhash算法

   

首先用O(N^2)的时间把所有pair存入hash表,一个pair也就是两两数组成的一对pair,一共有n(n-1)/2个,所以需要n平方的时间复杂度

   

根据什么来做hash呢,也就是说hashmap中的key值是什么,我们将一个pair的和作为key值,而value值就是这两个树组成的pairlist数据结构,map[hashvalue] = list,每个list中的元素就是一个pair, hashvalue=这个pair的和

   

那么接下来求4sum就变成了在所有的pair value中求 2sum,这个就成了线性算法了,注意这里的线性又是针对pair数量(N^2)的线性,所以整体上这个算法是O(N^2),而且因为我们挂了list, 所以只要符合4sum的我们都可以找到对应的是哪四个数字。

   

一连串数问题

   

因为是一串连续的数,那么结果就可以用一个small和一个big来界定连续数的第一个和最后一个数

   

int small=1;

int big=2;

   

另外samll的大小不必循环遍历到n,因为s/2+s/2+1>s,所以small<(s+1)/2

   

curSum可以用求和公式求出来(small+big)/2

   

如果相等,输出结果,如果大于small++,如果小于big++

   

while(small<(s+1)/2)

{

int curSum=0;

for(int i=small;i<=big;i++)

curSum+=i;

if(curSum==s)

{

System.out.println("find one");

for(int i=small;i<=big;i++)

System.out.println(i);

small++;

}

else

{

if(curSum>s)

small++;

else

big++;

}

}

   

另外要注意判断一下targets是否小于3,如果小于3,那么直接返回,因为输入的小于3的无意义,因为1+2就等于3了,而且至少输入两个数

posted @ 2015-05-03 10:00  keedor  阅读(114)  评论(0编辑  收藏