編程之法:面試和算法心得(尋找和爲定值的兩個數)

內容所有來自編程之法:面試和算法心得一書,實現是本身寫的使用的是java

題目描述

輸入一個數組和一個數字,在數組中查找兩個數,使得它們的和正好是輸入的那個數字。java

要求時間複雜度是O(N)。若是有多對數字的和等於輸入的數字,輸出任意一對便可。面試

例如輸入數組一、二、四、七、十一、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))。spa

能夠繼續優化作到時間O(N)麼?指針

解法一

根據前面的分析,a[i]在序列中,若是a[i]+a[k]=sum的話,那麼sum-a[i](a[k])也必然在序列中。 舉個例子,以下: 原始序列:code

  • 一、 二、 四、 七、十一、15

用輸入數字15減一下各個數,獲得對應的序列爲:blog

  • 1四、1三、十一、八、四、 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])也必然在序列中。 舉個例子,以下: 
     * 原始序列:一、 二、 四、 七、十一、15
     * 用輸入數字15減一下各個數,獲得對應的序列爲:
     * 1四、1三、十一、八、四、 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]);
            }
        }
    }
相關文章
相關標籤/搜索