引言
第一问题很简单,leetcode上也有相应的题,2Sum问题,leetcode上还有2Sum的进阶版3Sum,只是在这个问题上增加了一层循环而已,另外还有3Sum_Close问题,就是和接近s的三个数,本文将依次介绍2Sum问题,3Sum问题,以及3Sum_close,以及一串连续数问题
对于3Sum的问题,本文除了常用的退化为2Sum的n平方的解法外,还提供了一种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中取出来放入result,result也是个链表,相当于最终的结果是个链表,每个节点是一个解,每个解是一个链表,这个链表中有三个数
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;
}
2Sum和3Sum的时间复杂度分析
我们可以很轻易的就知道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就会有更快的一个算法。
4sum的hash算法
首先用O(N^2)的时间把所有pair存入hash表,一个pair也就是两两数组成的一对pair,一共有n(n-1)/2个,所以需要n平方的时间复杂度
根据什么来做hash呢,也就是说hashmap中的key值是什么,我们将一个pair的和作为key值,而value值就是这两个树组成的pair的list数据结构,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++;
}
}
另外要注意判断一下target的s是否小于3,如果小于3,那么直接返回,因为输入的小于3的无意义,因为1+2就等于3了,而且至少输入两个数