1. 兩數之和
總體的思路還是比較簡單的,也就是用一個字典記錄下我需要的值,如果在接下來的值中有匹配的值,就完成了目標,我在這裡就不考慮這些了,在這裡還是要考慮一些特殊的case和特殊的要求。
首先來看一下最簡單的版本,寫出這個版本就意味著面試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的時候線判斷一下次數,就可以知道使用幾次了。
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,之後只要遇到相同的,都直接略過,就完成了目標。(這裡有點像訊號量和獨佔鎖的關係有木有)
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向中間靠攏,直到相遇。
那麼在這裡便可以進行去重,如果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裡面大神的解法,異常優秀
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,不知道什麼地方還可以再優化,看了前排程式碼,也就差不多是這些優化了,其餘的可能更細節了。