最近碰到這樣一個問題,輸入一個已排序的數組和一個數字,在數組中查找兩個數,使得它們的和正好是輸入的那個數字,要將全部的組合列出來。javascript
拿到這個問題,我第一時間想到的方法是用雙重 for 循環遍歷數組,窮舉出全部的組合,判斷是否等於目標值。java
for (let i = 0; i < array.length - 1; i++) { for (let j = i + 1; j < array.length; j++) { if(a[i] + a[j] === sum) { ... } } }
這種方法是能解決這個問題的,可是時間複雜度是 O(N^2),那麼有沒有效率更高的方法呢?數組
既然要查找使 a + b = sum
的全部狀況,咱們能夠遍歷整個數組,去查找 sum - a
,那麼如今問題變爲在一個數組中去查找一個給定的數,而數組是通過排序的,咱們很天然的想到了二分搜索。優化
const arr = [1,2,4,6,7,9,10] const sum = 8 function twoSum (arr, sum) { const result = [] for (let i = 0, len = arr.length; i < len; i++) { const index = binarySearch(arr, sum - arr[i]) if (index !== -1 && i !== index) { result.push([arr[i], sum - arr[i]]) arr.splice(index, 1) } } return result } function binarySearch(arr, value) { let startIndex = 0, stopIndex = arr.length - 1, middle = Math.floor((stopIndex + startIndex) / 2); while (arr[middle] != value && startIndex < stopIndex) { if (value < arr[middle]) { stopIndex = middle - 1; } else { startIndex = middle + 1; } middle = Math.floor((stopIndex + startIndex) / 2); } return (arr[middle] !== value) ? -1 : middle; } twoSum(arr, sum)
這種方法只用了一次 for 循環,二分搜索的時間複雜度是 O(logN),因此整個方法的時間複雜度是 O(NlogN),比第一種方法效率更高。指針
那麼問題來了,這是效率最高的方法嗎?能不能只循環一次數組就找到全部組合呢,這樣時間複雜度就是 O(N)。用兩個指針 i 和 j,各自指向數組的首尾兩端,令 i = 0
,j = n - 1
,而後 i++
,j--
,逐次判斷 a[i] + a[j] === sum
。code
const arr = [1,2,4,6,7,9,10] const sum = 8 function twoSum (arr, sum) { const result = [] let i = 0 j = arr.length - 1 while (i < j) { if (arr[i] + arr[j] > sum) { j-- } else if (arr[i] + arr[j] < sum) { i++ } else { result.push([arr[i], arr[j]]) i++ j-- } } return result } twoSum(arr, sum)
隨着咱們的不斷優化,解決方法的時間複雜度由 O(N^2) 變爲 O(N)。排序
接下來咱們擴展一下,若是在數組中查找三個數,使得它們的和等於目標值,該怎麼解決。
這裏增長了一個變量,兩個數的狀況咱們是查找 sum - a
,三個數咱們能夠當作是在除去 a 的數組中查找兩個數使得 b + c = sum - a
,這樣一來問題就降維成兩個數的狀況。three
const arr = [1,2,4,6,7,9,10] const sum = 12 function twoSum (arr, sum, el) { const result = [] let i = 0 j = arr.length - 1 while (i < j) { if (arr[i] + arr[j] > sum) { j-- } else if (arr[i] + arr[j] < sum) { i++ } else { result.push([el, arr[i], arr[j]]) i++ j-- } } return result } function threeSum (arr, sum) { let result = [] for (let i = 0, len = arr.length; i < len; i++) { const newArr = arr.slice(i + 1) result = result.concat(twoSum(newArr, sum - arr[i], arr[i])) } return result } threeSum(arr, sum)
以上是對我這個問題的一些思考,各位讀者若是有更簡潔的方法,歡迎在評論區當中給出。ip