尋找陣列中和為定值的兩個數

JonnSnow發表於2019-03-03

文章同步在我的 知乎專欄segmentfault 專欄,歡迎關注。

最近碰到這樣一個問題,輸入一個已排序的陣列和一個數字,在陣列中查詢兩個數,使得它們的和正好是輸入的那個數字,要將所有的組合列出來。

拿到這個問題,我第一時間想到的方法是用雙重 for 迴圈遍歷陣列,窮舉出所有的組合,判斷是否等於目標值。

方法一

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 = 0j = n - 1,然後 i++j--,逐次判斷 a[i] + a[j] === sum

方法三

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,這樣一來問題就降維成兩個數的情況。

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)
複製程式碼

以上是對我這個問題的一些思考,各位讀者如果有更簡潔的方法,歡迎在評論區當中給出。

相關文章