【簡單演算法】1.兩數之和,給定整數陣列和目標值,找出陣列中2數之和等於目標值的元素

把蘋果v咬哭發表於2020-12-17

接觸了程式碼,那麼演算法始終是繞不開的一個重點。

演算法對於開發人員,在日常之中的作用很大,但是對於測試人員來說,實際編碼中用到的似乎不是很多。
不過,現在大廠的測試開發的面試,演算法是必考的,而且這也的確是你的程式碼功底的一項重要體現,學學沒壞處。

關於演算法的基礎知識,之前自己也買過書,但是學習的斷斷續續的,練習刷題就更加稀少了。
所以,打算日後做一個【簡單演算法】的記錄:

  • 第一,是為了梳理解題思路,加深鞏固。
  • 第二,在學習解題的過程中,將薄弱的程式碼環節、演算法基礎補全。
  • 第三,算是對演算法練習的一個督促。

題目來自LeetCode傳送門,有興趣的童鞋可以到上面刷題練習。

一、題目:兩數之和

描述

給定一個整數陣列 nums 和一個目標值 target,請你在該陣列中找出和為目標值的那 兩個 整數,並返回他們的陣列下標。

你可以假設每種輸入只會對應一個答案。但是,陣列中同一個元素不能使用兩遍。

示例

給定 nums = [2, 7, 11, 15], target = 9

因為 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]

解題

突然有了考試做題的感覺,我覺得首先題目要先審清楚,然後自己嘗試用自己已有的知識去解決。
實在做不出來,也別洩氣,演算法道路一定是曲折的,起碼對我來說是這樣,大佬除外。

另外,不管做不做出來,都要去學習下示例解法,學習解題思路,從中收穫更多。

1. 解法1

我自己嘗試著做,這題因為屬於簡單難度,我用for迴圈的知識進行了暴力破解,程式碼以python3為例:

def twoSum(nums, target):

    for i in range(len(nums)):
        for j in range(i+1, len(nums)):
            if nums[i] + nums[j] == target:
                return [i, j]

其實就是2次迴圈,最外層的迴圈,從列表第一個開始遍歷,直到最後一個元素,長度就是len(nums)
因為題目中說,單元素不能使用兩次,所以內層的迴圈,就從i 之後也就是i+1開始,直到最後一個元素。

拿到了2個數,就進行相加操作,與target進行比較,如果相等,就返回出這2個元素的下標。

執行一下:

def twoSum(nums, target):

    for i in range(len(nums)):
        for j in range(i+1, len(nums)):
            if nums[i] + nums[j] == target:
                return [i, j]


if __name__ == "__main__":
    print(twoSum2([2, 15, 11, 7], 9))

-------------結果----------------------
D:\Programs\Python\Python36\python.exe D:/練習/leecode/two_sum.py
[0, 3]

Process finished with exit code 0

這題雖然我做出來,但是這個解法並不好,如果遇到一個元素很多的列表,並且最後的2個值 之和 等於目標值,那麼這種情況下,
陣列裡的任意2個元素都要匹配比較一次。

時間複雜度就為:O(N^2)。
空間複雜度還好,因為沒去去開闢額外的空間去計算,所以是:O(1)。

關於複雜度的分析,後面單獨寫一篇介紹。

2. 解法2

上面的解法缺點就是在最壞的時候,陣列裡的任意2個元素都要匹配比較一次,那麼就要來解決這個問題。

換個思路來想,遍歷列表的中的元素x,如果列表中存在 target-x,那麼這2個數的下標就是最終我們要的結果。
官方的建議解法用了雜湊表,對於key-value這樣的儲存形式,x跟它的下標是對應的,這樣一來,找到target-x的時間複雜度就變成了O(1)。

所以新的解法就是:

def twoSum2(nums, target):
    hashtable = dict()
    for i, num in enumerate(nums):
        if target - num in hashtable:
            return [hashtable[target - num], i]
        hashtable[nums[i]] = i
    return []

這裡使用python,可以建立一個空字典。再利用python中的enumerate()函式,遍歷出列表裡的元素和下標。
在每一次的遍歷中,就可以用目標值 target —— 當前元素 num,判斷這個值 在不在字典裡。
這裡用到的是Python 字典 in 操作符,用於判斷鍵是否存在於字典中。

如果在的話,那就返回 字典裡的 元素以及,下標。
因為剛開始迴圈的的時候,字典裡沒資料,所以當每次迴圈後,我們要把這次迴圈的元素跟它的下標 分別 作為 key和value放到字典裡去。

可以加個列印看下 字典的操作過程:

def twoSum2(nums, target):
    hashtable = dict()
    for i, num in enumerate(nums):
        if target - num in hashtable:
            return [hashtable[target - num], i]
        hashtable[nums[i]] = i
        print(hashtable)
    return []


if __name__ == "__main__":
    print(twoSum2([4, 15, 3, 7, 2], 9))

=============================結果==============================
D:\Programs\Python\Python36\python.exe D:/練習/leecode/two_sum.py
{4: 0}
{4: 0, 15: 1}
{4: 0, 15: 1, 3: 2}
{4: 0, 15: 1, 3: 2, 7: 3}
[3, 4]

Process finished with exit code 0

最終,分析解法2的複雜度:

時間複雜度—— O(N),N 是列表中的元素數量。對於每一個元素 x,我們可以 O(1) 地尋找 target - x。
空間複雜度—— O(N),其中 N 是陣列中的元素數量。主要是雜湊表的開銷,在空間上的消耗。

其實也不能說解法1就是最爛的,因為演算法沒有最好的演算法,只有最適合的演算法。
隨著需求在空間和時間的取捨的不同,具體決定使用哪種演算法也是不同的。

二、小結

在這個簡單演算法裡,學習和回顧到的知識點:

  • 雜湊表的優點
  • python的enumerate()函式的運用
  • Python 字典 in 操作符,用於判斷鍵是否存在於字典中

相關文章