Python陣列中求和問題

技術小能手發表於2018-08-06

本專題主要介紹雜湊表和指標兩種方法來解決該類問題,從兩個數之和引申到三個數之和,再從四個數之和的問題上思考如何構建出一種通用的程式碼(可以解決N個數之和)。本文主要內容是通過001問題來初步瞭解陣列求和的兩種常用方法。

001-Two Sum

給定一個整數陣列和一個目標值,找出陣列中和為目標值的兩個數。 你可以假設每個輸入只對應一種答案,且同樣的元素不能被重複利用。

示例 :

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

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

  1. 暴力迴圈

O(n^2)

唯一需要注意的是同一個元素不能複用

nums_len = len(nums)
for
 i 
in
 range(
0
, nums_len):
    
for
 j 
in
 range(i + 
1
, nums_len):
        
if
 nums[i] + nums[j] == target:
            
return
 [i, j]
  1. 雜湊

(1) O(n)

(2) 考慮暴力迴圈中我們做的事情,我們先挑出一個值a,然後看陣列中其他值是否能與值a相加等於目標,也可以說成看陣列中是否存在一個值等於目標值減去值a。

(3) 換個思路,我們將所有遍歷過的值存放起來,每次遍歷到一個新的值b時,我們可以查詢目標值減去值b是否在我們存放的值中。基於雜湊表的特性,查詢的時間複雜度為O(1),總時間複雜度就變為了一次for迴圈O(n)

回到本道題中:

(1) 由於需要返回對應的索引,所以需要使用HashMap(在python中是dict),key存放陣列中的值,value存放陣列中的索引,遍歷陣列,將遍歷過的值存入dict,如果目標值減去當前值在dict中則證明找到了目標值。

(2) 還有一點需要注意的是如果想按從小到大的順序返回值,dict中存放的肯定是前一個值(因為是之前遍歷過的)。

seen_dict = {} 
for
 i, num 
in
 enumerate(nums):
    search = target - num
    
if
 search 
in
 seen_dict:
        
return
 [seen_dict[search], i]
    
else
:
        seen_dict[num] = i
  1. 雙指標

(1) O(nlogn)-主要是快排的影響

(2) 在一個有序的陣列中最左邊一定是最小值,而最右邊一定是最大值。我們可以將最小值與最大值相加與目標值進行比較,如果兩數之和大於目標值,我們就讓最大值小一點(也就是讀取第二個最大值),相反如果小於,則讓最小值大一點(讀取第二個最小值)。這樣我們就保證了一次迴圈就能查詢到目標值,但陣列必須是有序的。

回到題目中:

(1) 由於需要返回索引,所以我們必須儲存兩個陣列,一個是無序的(用於查詢真實的索引),另一個是有序的(用於查詢符合題目的值)。

(2) 兩個指標left和right分別指向陣列中第一個元素和最後一個元素(最小值和最大值)

(3) 迴圈的結束條件為左指標大於等於右指標(左邊的不能比右邊的大,而且一個元素只能用一次)

(4) 然後就判斷左值+右值與目標值之間的關係,在上面我們已經討論過了大於和小於的情況。

(5) 當等於時由於我們需要得到左值和右值在原本陣列的索引,我們需要考慮以下問題。從題目中的得知每個target只有一個答案, 意味著如果target是6不會出現[2, 2, 4]的情況, 但是會出現[3, 3]的情況, 也就是當兩個相同的值滿足情況是才會有重複的元素。所以我們先通過index獲取左值對應的索引,如果左值和右值相同我們就獲取下一個該值的索引,如果不同,我們直接獲取右值相關的索引。

raw_nums = nums
nums = sorted(nums)
left, right = 
0
, len(nums) - 
1
   
while
 left < right:
    v_left, v_right = nums[left], nums[right]
    two_sum = v_left + v_right
    
if
 two_sum > target:
        right -= 
1
    
elif
 two_sum < target:
        left += 
1
    
else
:  
# 找到了
        left_index = raw_nums.index(v_left)
        
# 如果值相同就查詢下一個該值的索引
        right_index = raw_nums.index(v_right, left + 
1
) 
if
 v_right == v_left 
else
 raw_nums.index(v_right)
        
return
 [left_index, right_index]

總結

通過兩個數求和問題初步瞭解陣列求和問題,下一文將引申這兩種方法在三個數求和中的應用。

原文釋出時間為:2018-08-04
本文作者:dyq666
本文來自雲棲社群合作伙伴“ Python中文社群”,瞭解相關資訊可以關注“ Python中文社群


相關文章