2 sum, 3 sum, 4sum以及python collections.Counter
最近的文章都是有關面試最常出到的100題
許多面試好像都喜歡問這三兄弟。
2 sum
給個列表,和target,返回列表中兩個數加起來等於這個target的index
舉例:
Given nums = [2, 7, 11, 15], target = 9,
Because nums[0] + nums[1] = 2 + 7 = 9,
return [0, 1].
新建一個空字典,遍歷一邊列表,如果元素不在字典裡,加進去(target-nums[i]:i),這樣如果以後有元素在字典裡說明我們找到了nums[j] = target-nums[i]。
時間複雜度O(n),因為在每一層迴圈中查詢字典都是常數時間(這個需要拓展,開一篇部落格討論hashmap和字典以及他們在python中的應用)
程式碼如下:
if len(nums) <= 1:
return False
buff_dict = {}
for i in range(len(nums)):
if nums[i] in buff_dict:
return [buff_dict[nums[i]], i]
else:
buff_dict[target - nums[i]] = i
3 sum
比如3 sum,其問題就是給你一個長列表, nums = [-1, 0, 1, 2, -1, -4], 再給你一個target = 0, 請輸出這個列表列表中相加等於0的三個數
示例答案是這個:
[
[-1, 0, 1],
[-1, -1, 2]
]
因為-1+0+1 = 0, -1-1+2 = 0
最慢的方法寫三重迴圈,顯然時間複雜度高了
如何減少複雜度
先排好序,然後最外層迴圈遍歷第一個數,後面兩個數怎麼確定呢,j,k在每次最外層迴圈開始時,取i+1和len(nums)-1,就是i之後的一頭一尾。nums[i]+nums[j]+nums[k]和0來比,因為陣列排好序了,所以如果小於0那就是nums[j]不夠大,我們把j後移一個,如果大於0那就是nums[k]太大了,我們把k前移一個。
在自己手動敲程式碼時犯下了如下錯誤:
1.我想用寫在for迴圈下的while迴圈裡的continue結束外層的本次for迴圈。。但是continue就是結束它所處的領域的本次迴圈
2.對於只要然後重複的理解不夠
3.在while中進行判斷了後,直接要在if body裡面寫迴圈的變化條件呀!不要搞成死迴圈了呀!
最後3sum程式碼如下(時間複雜度O(n2
res = []
nums.sort()
for i in range(len(nums)-2):
#考慮過的i就不用再考慮了
if i > 0 and nums[i] == nums[i-1]:
continue
#第二第三個數:一頭一尾
l, r = i+1, len(nums)-1
while l < r:
s = nums[i] + nums[l] + nums[r]
if s < 0:
l +=1
elif s > 0:
r -= 1
#分支應該寫清楚
else:
res.append([nums[i], nums[l], nums[r]])
#考慮過的l就不要考慮了
while l < r and nums[l] == nums[l+1]:
l += 1
while l < r and nums[r] == nums[r-1]:
r -= 1
#請記住一定要寫這個變化條件。。
l += 1; r -= 1
return res
4 sum II
給我們4個列表ABCD,從每個列表裡挑1個數加起來等於0,有幾種這樣的組合?
Input:
A = [ 1, 2]
B = [-2,-1]
C = [-1, 2]
D = [ 0, 2]
Output:
2
Explanation:
The two tuples are:
1. (0, 0, 0, 1) -> A[0] + B[0] + C[0] + D[1] = 1 + (-2) + (-1) + 2 = 0
2. (1, 1, 0, 0) -> A[1] + B[1] + C[0] + D[0] = 2 + (-1) + (-1) + 0 = 0
這個看起來有點頭疼,但是pythonic兩行程式碼解決這個計數問題
import collections
AB = collections.Counter(a+b for a in A for b in B)
return sum(AB[-c-d] for c in C for d in D)
python collections模組
collections是Python內建的一個集合模組,提供了許多有用的集合類。常用型別有:
計數器(Counter)
雙向佇列(deque)
預設字典(defaultdict)
有序字典(OrderedDict)
可命名元組(namedtuple)
我們的4 sum II 就用到了collections模組裡的Counter
Counter作為字典dict()的一個子類用來進行hashtable計數,將元素進行數量統計,計數後返回一個字典,鍵值為元素,值為元素個數
在我們用Counter(a+b for a in A b in B)後我們得到的是:
Counter({0: 2, -1: 1, 1: 1})
A和B列表中每個元素互相相加的結果有2個0,1個-1,1個1
接下來我們只用找C D與其對應的組合就好了。
for c in C d in D是對C D列表中元素組合的遍歷,把-c-d作為字典的鍵值,如果沒存到字典裡(說明這一次找到的a+b+c+d != 0)這一點又與普通的字典不一樣,普通的字典如果你訪問它沒存的鍵值會報錯
比如B = {1:2, 3:4}, 你想訪問B[0],會報錯
總結一下:
2 sum用的字典,將潛在的可能性一一存入,直到發現那個可以配對上的元素
3 sum,排序用的很好,先固定住第一個元素,後兩個元素的移動是根據三個數的和與0的比較來進行的,大於0了,那麼第三個元素太大,要往前移,小於0,第二個元素要往後移動。對於重複元素的跳過也是一大亮點
4 sum II,用的counter很巧妙,先counter出前兩個列表所有和的可能性與其對應的個數,然後對於後兩個列表所有和的可能性進行配對,其實思想挺像2 sum的