幾數之和分析,解法,優化和總結

 

1. 兩數之和
面試

2.三數之和,最近的三數之和編程

3.四數之和數組

1. 兩數之和

整體的思路仍是比較簡單的,也就是用一個字典記錄下我須要的值,若是在接下來的值中有匹配的值,就完成了目標,我在這裏就不考慮這些了,在這裏仍是要考慮一些特殊的case和特殊的要求。app

首先來看一下最簡單的版本,寫出這個版本就意味着面試gg。優化

def twoSum1(list1,target):
res = []
dic1 = {}
for i in list1:
if i not in dic1:
dic1[target-i]=i
else:
res.append([i,dic1[i]])
return res

print(twoSum([1,1,2,2,2,3,4,5],3))
[[2, 1], [2, 1],[2,1]]

從這個代碼中咱們很輕鬆的就能夠看出不少漏洞。若是數字中有重複的值應該怎麼辦呢?就像下面顯示的,若是list1裏面有兩個1 1 2 2 2 就是很明顯的漏洞,若是我規定每一個數字只能用一遍應該怎麼辦呢。對這個的改進就是在字典中保存咱們的次數,在else裏面append的時候線判斷一下次數,就能夠知道使用幾回了。spa

def twoSum(list1,target):
    res = []
    dic1 = {}
    for i in list1:
        if i not in dic1:

            if target - i in dic1:
                dic1[target-i] = (i,dic1[target-i][1]+1) # 每有一個i,那麼target-i在字典中的值就會多1
            else:
                dic1[target-i]=(i,1)   
        else:
            if dic1[i][1] >=1:
                res.append([i,dic1[i][0]])
                dic1[i] = (dic1[i][0],dic1[i][1]-1)
    return res
print(twoSum([1,1,2,2,2,3,4,5],3))
[[2, 1], [2, 1]]

從這裏能夠看出,咱們已經有效的解決了次數只能用一遍的狀況,還有一種狀況,就是要求全不能重複。也就是對於這種狀況,只能有一個[2,1]。 首先咱們能夠只須要在上面的字典中作一點手腳就能夠作到,也就是在字典的次數的統計,若是我把次數最大值也就設置成1,若是使用過就變成0,以後只要遇到相同的,都直接略過,就完成了目標。(這裏有點像信號量和獨佔鎖的關係有木有)code

def twoSum(list1,target):
    res = []
    dic1 = {}
    for i in list1:
        if i not in dic1:
            if target - i in dic1:
                if dic1[target-i] == 0:
                    continue
            dic1[target-i]=(i,1)
        else:
            if dic1[i][1] ==1:
                res.append([i,dic1[i][0]])
                dic1[i] = (dic1[i][0],dic1[i][1]-1)
    return res

 

2. 三數之和

三數之和和兩數之和有點像的,就是須要把數組多遍歷一遍。裏面主要的難點就是在於重複值的去除。若是按照上面利用字典的方式,對於重複值的去除是不太容易的事情。例如 3 2 1 3 這種排列,就可能致使重複的值。所以大體的思路就是先將數組排序,而後能夠依賴排序的性質去尋找目標的三個值。咱們先定義i,left,right三個數,其中i爲數組的沒一個元素.left,right分別爲i右邊的最左元素和最右元素。讓left和right向中間靠攏,直到相遇。blog

那麼在這裏即可以進行去重,若是list1[left] == list1[left+1] 那麼就要直接跳過。但在這裏編程有一個細節,就是對於[0,0,0]這種狀況,若是先判斷跳過,那麼咱們的值就位空了,因此應該先將目標值加入res中,而後將left和right進行更新。排序

def threeSum(list1):
    list1 = sorted(list1)
    N1 = len(list1)
    res = []
    for i in range(N1):
        if list1[i] > 0:  return  res
        if i > 0 and list1[i] == list1[i-1]:continue
        left = i+1
        right = N1-1
        while left < right:
            if list1[i] + list1[right]+list1[left] == 0:
                res.append([list1[i] ,list1[right],list1[left]])
                while left < right and list1[left] == list1[left+1]:
                    left+=1
                while left < right and list1[right] == list1[right-1]:
                    right -= 1
                left += 1
                right -= 1
          continue if list1[i] + list1[right]+list1[left] > 0: while left < right and list1[right] == list1[right-1]: right -= 1 right -=1
          continue if list1[i] + list1[right]+list1[left] < 0: while left < right and list1[left] == list1[left+1]: left+=1 left += 1 return res

 

這裏有沒有能夠優化的點呢? 顯示有的。既然有了這個兩數之和的那種思路,使用字典能夠造成一個空間換時間的概念。這裏直接用leetcode裏面大神的解法,異常優秀three

def threeSum(nums]):
        
        answers = set()
        
        negative_nums = [n for n in nums if n < 0]
        positive_nums = [n for n in nums if n > 0]
        zeros = [n for n in nums if n==0]
        negative_num_set = set(negative_nums)
        positive_num_set = set(positive_nums)
        
        if len(zeros)>0:
            if len(zeros)>=3:
                answers.add((0,0,0))
            for pos in positive_num_set:
                if -pos in negative_num_set:
                    answers.add((-pos,0,pos))
        
        
        
        for i in range(len(negative_nums)):
            for j in range(i):
                c = -(negative_nums[i] + negative_nums[j])
                if c in positive_num_set:
                    answers.add((min(negative_nums[i],negative_nums[j]),max(negative_nums[i],negative_nums[j]),c))
        
        for i in range(len(positive_nums)):
            for j in range(i):
                c = -(positive_nums[i] + positive_nums[j])
                if c in negative_num_set:
                    answers.add((c,min(positive_nums[i],positive_nums[j]),max(positive_nums[i],positive_nums[j])))
                    
        return [list(t) for t in answers]

 

3. 四數之和

   事實上,四數之和和三數之和很是相似,也就是多了一個循環。

class Solution:
    def fourSum(self, nums, target) :
        res = []
        nums  = sorted(nums)
        N1 = len(nums)
        for i in range(N1):
            # if nums[i] > target:return res
            if i > 0 and nums[i] == nums[i-1]:continue
            tempTarget = target - nums[i]
            for j in range(i+1,N1):
                # if nums[j] > tempTarget:break
                if j > i+1 and nums[j] == nums[j-1]:continue
                left = j+1
                right = N1-1
                while left < right:
                    if nums[j]+nums[left]+nums[right] == tempTarget:
                        res.append([nums[i],nums[j],nums[left],nums[right]])
                        while left < right and nums[left] == nums[left+1]:
                            left += 1
                        while left < right and nums[right] == nums[right-1]:
                            right -=1
                        left += 1
                        right -=1
                        continue
                    if nums[j]+nums[left]+nums[right] > tempTarget:
                        while left < right and nums[right] == nums[right-1]:
                            right -=1
                        right -= 1
                        continue
                    if nums[j]+nums[left]+nums[right] < tempTarget:
                        while left < right and nums[left] == nums[left+1]:
                            left += 1
                        left += 1
        return res
print(Solution().fourSum([1,-2,-5,-4,-3,3,3,5],-11))

固然對於面試的時候要是能寫出來這個其實就已經挺不錯的了,可是這樣很是暴力,有沒有什麼能夠優化的地方呢?事實上是有的,主要就是再何時能夠跳出循環,由於有了排序的步驟,所以能夠對排序進行儘量的優化。

1.在第for循環中,若是num[i] + 3*nums[i+1] >target:那麼就能夠直接break,由於這樣能夠認爲後面無論取什麼元素都會大於target。這裏咱們爲何不能像3數之和直接判斷nums[i]呢?由於3數之和的要求是target = 0 ,因此能夠直接判斷nums[i] > 0 就能夠了。

2.相同的思路,對於nums[i] + 3*nums[-1] < target的狀況,就能夠認爲這個是不存在的,直接continue掉就能夠了。

2.對於每一個循環,並不須要for 到他的最後一個元素,只要是倒數第4個元素就ok了。

3.對於初始化元素的判斷。如長度小於4.

因此最終代碼是

class Solution:
    def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
        res = []
        nums  = sorted(nums)
        N1 = len(nums)
        if(nums is None or N1 < 4 ):
            return []
        for i in range(N1-3):
            if nums[i] + 3*nums[i+1] > target:return res
            if nums[i] + 3*nums[-1] < target: continue
            if i > 0 and nums[i] == nums[i-1]:continue
            tempTarget = target - nums[i]
            for j in range(i+1,N1-2):
                if nums[j]+2*nums[j+1] > target - nums[i]:break
                if nums[j] + 2*nums[-1] < target - nums[i]:continue
                if j > i+1 and nums[j] == nums[j-1]:continue
                left = j+1
                right = N1-1
                while left < right:
                    if nums[j]+nums[left]+nums[right] == tempTarget:
                        res.append([nums[i],nums[j],nums[left],nums[right]])
                        while left < right and nums[left] == nums[left+1]:
                            left += 1
                        while left < right and nums[right] == nums[right-1]:
                            right -=1
                        left += 1
                        right -=1
                        continue
                    if nums[j]+nums[left]+nums[right] > tempTarget:
                        while left < right and nums[right] == nums[right-1]:
                            right -=1
                        right -= 1
                        continue
                    if nums[j]+nums[left]+nums[right] < tempTarget:
                        while left < right and nums[left] == nums[left+1]:
                            left += 1
                        left += 1
        return res
                    

最後也只到達了124ms,不知道什麼地方還能夠再優化,看了前排代碼,也就差很少是這些優化了,其他的可能更細節了。

相關文章
相關標籤/搜索