輸入一個數組和一個數字,在數組中查找兩個數,使得它們的和正好是輸入的那個數字。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減一下各個數,獲得對應的序列爲:blog
第一個數組以一指針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,
因此,數組無序的時候,時間複雜度最終爲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]); } } }