• 剑指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了,而且至少输入两个数

  • 相关阅读:
    ASP.NET Web API +Swagger创建与汉化生成 API说明文档
    Apple 开发者账号 All In One
    CS50 2022 All In One
    TypeScript private field All In One
    js RegExp test bug All In One
    vite preview not work All In One
    Flutter Resources All In One
    table 组件性能优化 All In One
    Stanford CS193p All In One
    Swift 5.x bug All In One
  • 原文地址:https://www.cnblogs.com/keedor/p/4473334.html
Copyright © 2020-2023  润新知