1. 程式人生 > >2 sum, 3 sum, 4sum以及python collections.Counter

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的