zoukankan      html  css  js  c++  java
  • 编程之法:面试和算法心得(寻找和为定值的两个数)

    内容全部来自编程之法:面试和算法心得一书,实现是自己写的使用的是java

    题目描述

    输入一个数组和一个数字,在数组中查找两个数,使得它们的和正好是输入的那个数字。

    要求时间复杂度是O(N)。如果有多对数字的和等于输入的数字,输出任意一对即可。

    例如输入数组1、2、4、7、11、15和数字15。由于4+11=15,因此输出4和11。

    分析与解法

    咱们试着一步一步解决这个问题(注意阐述中数列有序无序的区别):

    直接穷举,从数组中任意选取两个数,判定它们的和是否为输入的那个数字。此举复杂度为O(N^2)。很显然,我们要寻找效率更高的解法

    题目相当于,对每个a[i],查找sum-a[i]是否也在原始序列中,每一次要查找的时间都要花费为O(N),这样下来,最终找到两个数还是需要O(N^2)的复杂度。那如何提高查找判断的速度呢?

    答案是二分查找,可以将O(N)的查找时间提高到O(log N),这样对于N个a[i],都要花logN的时间去查找相对应的sum-a[i]是否在原始序列中,总的时间复杂度已降为O(N log N),且空间复杂度为O(1)。 (如果有序,直接二分O(N log N),如果无序,先排序后二分,复杂度同样为O(N log N + N log N)= O(N log N),空间复杂度总为O(1))。

    可以继续优化做到时间O(N)么?

    解法一

    根据前面的分析,a[i]在序列中,如果a[i]+a[k]=sum的话,那么sum-a[i](a[k])也必然在序列中。 举个例子,如下: 原始序列:

    • 1、 2、 4、 7、11、15

    用输入数字15减一下各个数,得到对应的序列为:

    • 14、13、11、8、4、 0

    第一个数组以一指针i 从数组最左端开始向右扫描,第二个数组以一指针j 从数组最右端开始向左扫描,如果第一个数组出现了和第二个数组一样的数,即a[i]=a[j],就找出这俩个数来了。 如上,i,j最终在第一个,和第二个序列中找到了相同的数4和11,所以符合条件的两个数,即为4+11=15。 怎么样,两端同时查找,时间复杂度瞬间缩短到了O(N),但却同时需要O(N)的空间存储第二个数组。要注意的是,首先数组要排序,其次如果a[i]>a[j] i++,如果a[i]<a[j] j--

    /*
         * 根据前面的分析,a[i]在序列中,如果a[i]+a[k]=sum的话,那么sum-a[i](a[k])也必然在序列中。 举个例子,如下: 
         * 原始序列:1、 2、 4、 7、11、15
         * 用输入数字15减一下各个数,得到对应的序列为:
         * 14、13、11、8、4、 0
         * 第一个数组以一指针i 从数组最左端开始向右扫描,第二个数组以一指针j 从数组最右端开始向左扫描,
         * 如果第一个数组出现了和第二个数组一样的数,即a[i]=a[j],就找出这俩个数来了。 
         * 如上,i,j最终在第一个,和第二个序列中找到了相同的数4和11,所以符合条件的两个数,即为4+11=15。 
         * 两端同时查找,时间复杂度瞬间缩短到了O(N),但却同时需要O(N)的空间存储第二个数组。
         */
        public static void solution1(int arr[],int n)
        {
            Arrays.sort(arr);
            int[] temp = new int[arr.length];
            for(int i=0;i<arr.length;i++)
            {
                temp[i] = n-arr[i];
            }
            int start = 0;
            int end = arr.length-1;
            while(end>0&&start<arr.length)
            {
                if(arr[start]<temp[end])
                {
                    start++;
                }
                else if(arr[start]>temp[end])
                {
                    end--;
                }
                else {
                    System.out.println("num"+arr[start]);
                    start++;
                    end--;
                }
            }
        }

    解法二

    当题目对时间复杂度要求比较严格时,我们可以考虑下用空间换时间,上述解法一即是此思想,此外,构造hash表也是典型的用空间换时间的处理办法。

    即给定一个数字,根据hash映射查找另一个数字是否也在数组中,只需用O(1)的时间,前提是经过O(N)时间的预处理,和用O(N)的空间构造hash表。

    但能否做到在时间复杂度为O(N)的情况下,空间复杂度能进一步降低达到O(1)呢?

    /*
         * 构建hash表,存储另一个数字是否存在数组内
         */
        public static void solution2(int arr[],int n)
        {
            Map temp = new HashMap<Integer, Integer>();
            for(int i:arr)
            {
                temp.put(n-i, i);
            }
            for(int i:arr)
            {
                if(temp.containsKey(i))
                {
                    System.out.println("num"+i);
                }
            }
            
        }

    解法三

    如果数组是无序的,先排序(N log N),然后用两个指针i,j,各自指向数组的首尾两端,令i=0,j=n-1,然后i++,j--,逐次判断a[i]+a[j]?=sum,

    • 如果某一刻a[i]+a[j] > sum,则要想办法让sum的值减小,所以此刻i不动,j--;
    • 如果某一刻a[i]+a[j] < sum,则要想办法让sum的值增大,所以此刻i++,j不动。

    所以,数组无序的时候,时间复杂度最终为O(N log N + N)=O(N log N)。

    如果原数组是有序的,则不需要事先的排序,直接用两指针分别从头和尾向中间扫描,O(N)搞定,且空间复杂度还是O(1)。

    /*
         * 如果数组是无序的,先排序(N log N),然后用两个指针i,j,各自指向数组的首尾两端,令i=0,j=n-1,然后i++,j--,逐次判断a[i]+a[j]?=sum,
         * 如果某一刻a[i]+a[j] > sum,则要想办法让sum的值减小,所以此刻i不动,j--;
         * 如果某一刻a[i]+a[j] < sum,则要想办法让sum的值增大,所以此刻i++,j不动。
         * 所以,数组无序的时候,时间复杂度最终为O(N log N + N)=O(N log N)。
         * 如果原数组是有序的,则不需要事先的排序,直接用两指针分别从头和尾向中间扫描,O(N)搞定,且空间复杂度还是O(1)。
         */
        public static void solution3(int arr[],int n)
        {
            Arrays.sort(arr);
            int start = 0;
            int end = arr.length-1;
            while(end>0&&start<arr.length&&start<end)
            {
                if(arr[start]+arr[end]<n)
                {
                    start++;
                }
                else if(arr[start]+arr[end]>n)
                {
                    end--;
                }
                else
                {
                    System.out.println("num"+arr[start]+"nums"+arr[end]);
                }
            }
        }
  • 相关阅读:
    QSettings读写注册表、配置文件
    QSettings介绍
    桥接模式
    Qt之启动外部程序
    StringJDBC更改数据库的两种方式
    写代码注意事项
    maven仓库
    java关闭流,解压缩后的清除
    Java删除文件夹和文件
    浏览器禁用后退
  • 原文地址:https://www.cnblogs.com/icysnow/p/8260067.html
Copyright © 2011-2022 走看看