兩數之和是一道非常經典,也非常高頻的面試題,題目大意如下:
給定一個整數陣列nums
和一個目標值target
,請你在該陣列中找出和為目標值的那兩個整數,並返回他們的陣列下標。
case:
給定nums = [2, 1, 7, 11, 15], target = 9
因為nums[0] + nums[2] = 2 + 7 = 9
所以返回[0, 2]
今天我們就一起探討一下這道題的解法。
太長不看版
- 可以通過暴力運算,遍歷
nums
中的每一個元素,查詢陣列剩餘部分是否有匹配的值; - 更高效的方式是利用雜湊表key唯一且訪問快的特性,建立
map
儲存未命中的值。遍歷nums
中的元素,查詢map
上是否有匹配的目標值,否則將當前元素儲存到map
上。
暴力運演算法
暴力運演算法很簡單,通過首層for
迴圈遍歷陣列中的每個元素curr
,再通過另外一層for
迴圈尋找target - curr
值。
程式碼如下:
雙層for
迴圈導致暴力運演算法時間複雜度為 O(n2),在2020年的今天著實不能令大部分人滿意。
雜湊表法
雜湊表(也叫雜湊表)是一種資料結構,其定義如下:
雜湊表是根據關鍵字(Key value)而直接訪問在記憶體儲存位置的資料結構。
我們可以把它理解為詞典,詞典裡的每個詞條都是唯一的,在這個詞條後面記錄著詞條的含義。就像查詞典時我們能夠很快速的定位到詞條,雜湊表的訪問速度也非常快,其時間複雜度為O(1)。
在JavaScript
中常規的鍵值對物件就是雜湊表的實現。
接下來我們就結合題目中的case
通過圖解的方式來說明雜湊表法如何計算兩數之和。
0.初始化
初始化時我們會拿到nums
陣列[2, 1, 7, 11, 15]
和target
值9
,同時map
初始化為空物件{}
用於存放未匹配成功的鍵值對:
接下來將遍歷nums
陣列。
1.開始遍歷陣列
遍歷開始,此時:
- 當前
map
為空{}
; - 當前索引值
index
為0
; - 當前索引對應的數值
curr
為2
; - 我們希望找到
target - curr
即need
值為7
。
因為此時map
為{}
,所以map[need]
值為undefined
。
2.儲存未匹配成功的值至map
因為所求為兩數之和,所以哪怕nums[0]
的值等於target
,迭代第一步必然匹配失敗。
此時將未能成功匹配的curr
與index
存放到map
中。這一步非常重要,是整個解法的關鍵:
map[curr] = index;
這裡使用curr
做為key
,因為我們需要返回的結果是配對成功的數字其下標所構成的陣列,匹配時是在map
查詢數字、返回下標。
3.繼續遍歷陣列
匹配未成功,重複第一步,繼續遍歷陣列:
同樣未找到期望值need
,繼續將curr
和index
寫入map
:
繼續重複此步驟直至匹配成功。
4.匹配成功!
繼續遍歷nums
,此時need
為2
、curr
為7
,終於在map
中查詢到了need
,隔空會師成功!
返回結果:[map[need], index]
。map[need]
在前的原因是map
裡儲存的值,其下標一定在curr
對應的下標之前。
5.完整示例程式碼
- 雜湊解法每次查詢的時間複雜度是O(1),n次查詢的時間複雜度O(n);
- 空間複雜度也是O(n),
map
上最多存n個元素。
小結
- 雙層
for
迴圈暴力運算簡單直觀,時間複雜度O(n2)、空間複雜度O(1); - 雜湊表法時間複雜度和空間複雜度都是O(n);
- 考察點是對雜湊表這種資料結構的熟悉程度;
- 多一種解法就多一分勝算;
- 整體難度不高。
一入JS深似海,希望這個專欄能在你乘風破浪的旅途中有所幫助。歡迎關注我的公眾號:「JS漫步指南」,更多精彩等待您發現!