【Leetcode】 刷题之路1(python)
leetcode 刷題之路1(python)
看到有大佬總結了一些相關題目,想著先刷一類。
- 1.兩數之和
- 15.三數之和
- 16.最接近的三數之和
- 11.盛最多的水
- 18.四數之和
- 454.四數相加II
1. 兩數之和
給定一個整數數組 nums 和一個整數目標值 target,請你在該數組中找出 和為目標值 target 的那 兩個 整數,并返回它們的數組下標。
你可以假設每種輸入只會對應一個答案。但是,數組中同一個元素在答案里不能重復出現。
你可以按任意順序返回答案。
示例 1:
輸入:nums = [2,7,11,15], target = 9
輸出:[0,1]
解釋:因為 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
示例 2:
輸入:nums = [3,2,4], target = 6
輸出:[1,2]
示例 3:
輸入:nums = [3,3], target = 6
輸出:[0,1]
提示:
-2 <= nums.length <= 10^4
-10^9<= nums[i] <= 10^9
-10^9 <= target <= 10^9
只會存在一個有效答案
題解
看了大神的一些解體參考,暴力破解法雙層for循環查找的話,時間復雜度是O(n^2),會出現RuntimeOver。
方法一:用python的list相關函數求解
該題的解決方案是找到 num2 = target - num1是否在該list中,如果num2在list中查找出其下標,并返回。
- num2 in nums,返回 True
- nums.index(num2),查找 num2 的索引
這樣需要對于每一個num1,對整個nums列表做一次啊查詢,為了提高時間復雜度,num2 的查找并不需要每次從 nums 查找一遍,只需要從 num1 位置之前或之后查找即可。這里選擇從num1位置之前的元素中找。
def twoSum(nums, target):lens = len(nums)j=-1for i in range(1,lens):temp = nums[:i]if (target - nums[i]) in temp:j = temp.index(target - nums[i])breakif j>=0:return [j,i]
方法二:直接使用字典求解
用字典的方法進行求解,使用enumerate枚舉所有元素,省去了查找索引,效率很快
class Solution:def twoSum(self, nums: List[int], target: int) -> List[int]:records = dict() #創建空字典# 用枚舉更方便,就不需要通過索引再去取當前位置的值for idx, val in enumerate(nums):if target - val not in records:records[val] = idxelse:return [records[target - val], idx] # 如果存在就返回字典記錄索引和當前索引
方法三:用字典模擬哈希求解
參考了大神們的解法,通過哈希來求解,這里通過字典來模擬哈希查詢的過程。其實就是字典記錄了 num1 和 num2 的值和位置,不需要再去查詢一遍索引的步驟。
def twoSum(nums, target):hashmap={}for ind,num in enumerate(nums):hashmap[num] = indfor i,num in enumerate(nums):j = hashmap.get(target - num)if j is not None and i!=j:return [i,j]
小話癆:開始使用python刷題啦(最近在學機器學習),用leetcode邊刷題,邊練python,順便寫點小總結。
這題,讓我記住了python中list(列表)類型、dictonary(字典)類型、內置函數enumerate。
enumerate(sequence, [start=0])
15. 三數之和
給你一個包含 n 個整數的數組 nums,判斷 nums 中是否存在三個元素 a,b,c ,使得 a + b + c = 0 ?請你找出所有和為 0 且不重復的三元組。
注意:答案中不可以包含重復的三元組。
示例 1:
輸入:nums = [-1,0,1,2,-1,-4]
輸出:[[-1,-1,2],[-1,0,1]]
題解
雖然題目意思和兩數之和類似,但看了官方題解和做法后,發現用的方法不盡相同。
任意一個三元組的和都為 0。如果直接使用三重循環枚舉三元組,會得到 O(N3)個滿足題目要求的三元組(其中 NN 是數組的長度)時間復雜度至少為 O(N^3)。在這之后,我們還需要使用哈希表進行去重操作,得到不包含重復三元組的最終答案,又消耗了大量的空間。這個做法的時間復雜度和空間復雜度都很高,因此我們要換一種思路來考慮這個問題。
排序+雙指針
該提要求不重復的三元組,不重復,也就是說,我們枚舉的三元組 (a, b, c)需要滿足a ≤ b ≤ c,保證了只有 (a, b, c) 這個順序會被枚舉到。要實現這一點,我們可以將數組中的元素從小到大進行排序,隨后使用普通的三重循環就可以滿足上面的要求。
同時,對于每一重循環而言,相鄰兩次枚舉的元素不能相同,否則也會造成重復。舉個例子,如果排完序的數組為 [0, 1, 2, 2, 2, 3] ,我們使用三重循環枚舉到的第一個三元組為 (0, 1, 2),如果第三重循環繼續枚舉下一個元素,那么仍然是三元組 (0, 1, 2),產生了重復。因此我們需要將第三重循環跳到下一個不相同的元素,即數組中的最后一個元素 3,枚舉三元組 (0, 1, 3)。
這種方法的時間復雜度仍然為 O(N^3),畢竟我們還是沒有跳出三重循環的大框架。然而它是很容易繼續優化的,可以發現,如果我們固定了前兩重循環枚舉到的元素 a 和 b,那么只有唯一的 c 滿足 a+b+c=0。當第二重循環往后枚舉一個元素 b’ 時,由于 b’ > b,那么滿足 a+b’+c’=0 的 c’ 一定有 c’ < c,即 c’ 在數組中一定出現在 c 的左側。也就是說,我們可以從小到大枚舉 b,同時從大到小枚舉 c,即第二重循環和第三重循環實際上是并列的關系。
有了這樣的發現,我們就可以保持第二重循環不變,而將第三重循環變成一個從數組最右端開始向左移動的指針。并且,保持左指針一直在右指針的左側(即滿足 b≤c)。
這個方法就是我們常說的雙指針,當我們需要枚舉數組中的兩個元素時,如果我們發現隨著第一個元素的遞增,第二個元素是遞減的,那么就可以使用雙指針的方法。
(看了官方題解和大佬的題解,我認為同一種方法大佬的代碼更加簡單易理解)
class Solution:def threeSum(self, nums: List[int]) -> List[List[int]]:n=len(nums)if(not nums or n<3):return []nums.sort()res=[]for i in range(n):if(nums[i]>0): # 如果第一個數就大于0,排序后的兩個數不可能使得三個數=0return resif(i>0 and nums[i]==nums[i-1]):continueL=i+1R=n-1while(L<R):if(nums[i]+nums[L]+nums[R]==0):res.append([nums[i],nums[L],nums[R]])while(L<R and nums[L]==nums[L+1]):L=L+1 # b的指針右移動while(L<R and nums[R]==nums[R-1]):R=R-1 # c的指針左移# 找下一個符合條件的L=L+1R=R-1elif(nums[i]+nums[L]+nums[R]>0):R=R-1else:L=L+1return res
復雜度分析
- 時間復雜度:O(N^2),其中 N 是數組 nums 的長度。數組排序O(NlogN)
- 空間復雜度:O(logN)。我們忽略存儲答案的空間,額外的排序的空間復雜度為 O(logN)。然而我們修改了輸入的數組 nums,在實際情況下不一定允許,因此也可以看成使用了一個額外的數組存儲了nums 的副本并進行排序,空間復雜度為 O(N)。
16. 最接機的三數之和
給你一個長度為 n 的整數數組 nums 和 一個目標值 target。請你從 nums 中選出三個整數,使它們的和與 target 最接近。
返回這三個數的和。
假定每組輸入只存在恰好一個解。
題解
這道題雖與上一道相似,但實際上因為a+b+c=0有不少特殊性質,并不能完全借鑒思路。
這道題的題目比較短,獲取最接近target目標值的三數之和。
首先拿到這種題,我們要先看下提示中的取值范圍,3 <= nums.length <= 103條件標志著。不會出現不足三個數字的異常場景,但103 如果三層for循環109必然會超時,暴力解法不通。
那么,下來就需要考慮優化方案:
- 二分查找 or hash表?
二分查找和hash表只針對單個數字,那勢必我們需要先雙層循環再二分,10^6一樣會超時。 - 縮減條件
既然三個數字我們有些無從下手,那么先使用一層for循環,減少一個數字的篩選再來考慮是否就簡單了一些。
減少一個數字后,我們的題目變成了查找數組中某兩個最接近target - num1,就變成了一道兩數之和的基礎題。 - 實現方案
我們先將nums排序;
設置返回值res,初始為無窮大;
循環開始:從第一個數nums[i]后的數開始,設置left = i + 1, right = length - 1;tmp = nums[left] + nums[rigth]
比較 res和tmp+nums[i],哪個更接近target,并賦值給res
如果tmp = target - nums[i],表示找到了三個數等于target直接返回target
如果tmp > target - nums[i],我們將right向左移一個
如果tmp < target - nums[i],我們將left向右移一個
最終,即可獲取結果。
排序+雙指針
同時,借鑒上一道題,可以直接將其移動到下一個與這次枚舉到的不相同的元素,減少枚舉的次數,縮短時間。(但這樣會消耗內存)
class Solution:def threeSumClosest(self, nums: List[int], target: int) -> int:ret = float('inf')nums.sort()length = len(nums)for i in range(length - 2):left = i + 1right = length - 1while left < right:tmp = nums[i] + nums[left] + nums[right]ret = tmp if abs(tmp - target) < abs(ret - target) else ret #取更近的if tmp == target:return targetif tmp > target:r = right - 1# 移動到下一個不相等的元素while left < r and nums[r] == nums[right]:r -= 1right = r# right- =1else:# 如果和小于 target,移動 left 對應的指針l = left + 1# 移動到下一個不相等的元素while l < right and nums[l] == nums[left]:l += 1left = l#left- =1return ret
11. 盛最多水的容器
給你 n 個非負整數 a1,a2,…,an,每個數代表坐標中的一個點 (i, ai) 。在坐標內畫 n 條垂直線,垂直線 i 的兩個端點分別為 (i, ai) 和 (i, 0) 。找出其中的兩條線,使得它們與 x 軸共同構成的容器可以容納最多的水。
說明:你不能傾斜容器。
示例:
輸入:[1,8,6,2,5,4,8,3,7]
輸出:49
解釋:圖中垂直線代表輸入數組 [1,8,6,2,5,4,8,3,7]。在此情況下,容器能夠容納水(表示為藍色部分)的最大值為 49。
題解
這一題使用:雙指針。
容納的水量=兩個指針指向的數字中較小值*指針之間的距離
至于怎么移動前后兩個指針?
如果我們移動數字較大的那個指針,那么前者「兩個指針指向的數字中較小值」不會增加,后者「指針之間的距離」會減小,那么這個乘積會減小。因此,我們移動數字較大的那個指針是不合理的。因此,我們移動數字較小的那個指針。
以示例為例:在初始時[1, 8, 6, 2, 5, 4, 8, 3, 7],左右指針分別指向數組的左右兩端,它們可以容納的水量為 min(1, 7) * 8 =8。我們將左指針向右移動,此時可以容納的水量為 min(8, 7) * 7 =49。
class Solution:def maxArea(self, height: List[int]) -> int:l, r = 0, len(height) - 1ans = 0while l < r:area = min(height[l], height[r]) * (r - l)ans = max(ans, area)if height[l] <= height[r]:l += 1else:r -= 1return ans
18. 四數之和
給你一個由 n 個整數組成的數組 nums ,和一個目標值 target 。請你找出并返回滿足下述全部條件且不重復的四元組 [nums[a], nums[b], nums[c], nums[d]] (若兩個四元組元素一一對應,則認為兩個四元組重復):
- 0 <= a, b, c, d < n
- a、b、c 和 d 互不相同
- nums[a] + nums[b] + nums[c] + nums[d] == target
你可以按 任意順序 返回答案 。
題解
方法一
本題與「15. 三數之和」相似,解法也相似。排序+雙指針
為了避免枚舉到重復四元組,則需要保證每一重循環枚舉到的元素不小于其上一重循環枚舉到的元素,且在同一重循環中不能多次枚舉到相同的元素。
為了實現上述要求,可以對數組進行排序,并且在循環過程中遵循以下兩點:
- 每一種循環枚舉到的下標必須大于上一重循環枚舉到的下標;
- 同一重循環中,如果當前元素與上一個元素相同,則跳過當前元素。
使用上述方法,可以避免枚舉到重復四元組,但是由于仍使用四重循環,時間復雜度仍是 O(n^4)。注意到數組已經被排序,因此可以使用雙指針的方法去掉一重循環。
使用兩重循環分別枚舉前兩個數,然后在兩重循環枚舉到的數之后使用雙指針枚舉剩下的兩個數。假設兩重循環枚舉到的前兩個數分別位于下標 i 和 j,其中 i<j。初始時,左右指針分別指向下標 j+1 和下標 n-1。每次計算四個數的和,并進行如下操作:
- 如果和等于 target,則將枚舉到的四個數加到答案中,然后將左指針右移直到遇到不同的數,將右指針左移直到遇到不同的數;
- 如果和小于 target,則將左指針右移一位;
- 如果和大于 target,則將右指針左移一位。
具體實現時,還可以進行一些剪枝操作:
- 在確定第一個數之后,如果nums[i]+nums[i+1]+nums[i+2]+nums[i+3]>target,說明此時剩下的三個數無論取什么值,四數之和一定大于target,因此退出第一重循環;
- 在確定第一個數之后,如果nums[i]+nums[n?3]+nums[n?2]+nums[n?1]<target,說明此時剩下的三個數無論取什么值,四數之和一定小于 target,因此第一重循環直接進入下一輪,枚舉nums[i+1];
- 在確定前兩個數之后,如果nums[i]+nums[j]+nums[j+1]+nums[j+2]>target,說明此時剩下的兩個數無論取什么值,四數之和一定大于target,因此退出第二重循環;
- 在確定前兩個數之后,如果 nums[i]+nums[j]+nums[n?2]+nums[n?1]<target,說明此時剩下的兩個數無論取什么值,四數之和一定小于 target,因此第二重循環直接進入下一輪,枚舉 nums[j+1]。
class Solution:def fourSum(self, nums: List[int], target: int) -> List[List[int]]:quadruplets = list()if not nums or len(nums) < 4:return quadrupletsnums.sort()length = len(nums)for i in range(length - 3):if i > 0 and nums[i] == nums[i - 1]:continueif nums[i] + nums[i + 1] + nums[i + 2] + nums[i + 3] > target:breakif nums[i] + nums[length - 3] + nums[length - 2] + nums[length - 1] < target:continuefor j in range(i + 1, length - 2):if j > i + 1 and nums[j] == nums[j - 1]:continueif nums[i] + nums[j] + nums[j + 1] + nums[j + 2] > target:breakif nums[i] + nums[j] + nums[length - 2] + nums[length - 1] < target:continueleft, right = j + 1, length - 1while left < right:total = nums[i] + nums[j] + nums[left] + nums[right]if total == target:quadruplets.append([nums[i], nums[j], nums[left], nums[right]])while left < right and nums[left] == nums[left + 1]:left += 1left += 1while left < right and nums[right] == nums[right - 1]:right -= 1right -= 1elif total < target:left += 1else:right -= 1return quadruplets
復雜度分析
- 時間復雜度:O(n^3),其中 n 是數組的長度。
排序的時間復雜度是O(nlogn),枚舉四元組的時間復雜度是 O(n^3) - 空間復雜度:O(logn),其中 n 是數組的長度。
空間復雜度主要取決于排序額外使用的空間。此外排序修改了輸入數組 nums,實際情況中不一定允許,因此也可以看成使用了一個額外的數組存儲了數組 nums 的副本并排序,空間復雜度為O(n)。
方法二
看了大佬的解法,不用指針不用指針!!回溯解法
思路:
1.對所有數字來說,都有選擇,和不選擇兩種情況。
2. 選擇一個數,target就減去這個數,最后target == 0,并且只選擇了4個數就結束。
3. 不選擇這個數,就去檢查下一個數
剪枝很重要!!!
如果排序了數組nums之后。
- 當我們選了這個數,但是選了它之后,即時后面連選全數組最大的數,也不能達到target。就是說,我們當前這個數太小了。這時候,可以肯定這個數是一定不用選擇的。
target - nums[i] - (3 - len(oneSolution)) * nums[-1] > 0 - 我們當前的數組的基礎上,連續選擇當前這個數的話,整體的解的和會比target大,由于我們提前排序了nums,往后去尋找其他的數,只會比當前的數更大,所以這種情況下,我們就不需要往后再去找了。
target - (4 - len(oneSolution)) * nums[i] < 0
但該方法時間復雜度比方法一高高
class Solution:def fourSum(self, nums: List[int], target: int) -> List[List[int]]:nums.sort()output = []def Search(i, target, oneSolution, notSelected):if target == 0 and len(oneSolution) == 4:output.append(oneSolution)returnelif len(oneSolution) > 4 or i >= len(nums):returnif target - nums[i] - (3 - len(oneSolution)) * nums[-1] > 0 or nums[i] in notSelected:Search(i + 1, target, oneSolution, notSelected)elif target - (4 - len(oneSolution)) * nums[i] < 0:returnelse:Search(i + 1, target, oneSolution, notSelected + [nums[i]])Search(i + 1, target - nums[i], oneSolution + [nums[i]], notSelected)Search(0, target, [], [])return output
454.四數相加II
給你四個整數數組 nums1、nums2、nums3 和 nums4 ,數組長度都是 n ,請你計算有多少個元組 (i, j, k, l) 能滿足:
- 0 <= i, j, k, l < n
- nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0
示例:
輸入:nums1 = [1,2], nums2 = [-2,-1], nums3 = [-1,2], nums4 = [0,2]
輸出:2
解釋:
兩個元組如下:
(0, 0, 0, 1) -> nums1[0] + nums2[0] + nums3[0] + nums4[1] = 1 + (-2) + (-1) + 2 = 0
(1, 1, 0, 0) -> nums1[1] + nums2[1] + nums3[0] + nums4[0] = 2 + (-1) + (-1) + 0 = 0
題解
和18.四數之和不同的是,這題題是從四個數組中各取一個數。
且要求的輸出是一共有多少個元組
方法一
我們可以將四個數組分成兩部分,A 和 B 為一組,C 和 D 為一組。
對于 A 和 B,我們使用二重循環對它們進行遍歷,得到所有 A[i]+B[j] 的值并存入哈希映射中。對于哈希映射中的每個鍵值對,每個鍵表示一種 A[i]+B[j],對應的值為 A[i]+B[j] 出現的次數。
對于 C 和 D,我們同樣使用二重循環對它們進行遍歷。當遍歷到 C[k]+D[l] 時,如果 ?(C[k]+D[l]) 出現在哈希映射中,那么將 ?(C[k]+D[l]) 對應的值累加進答案中。
最終即可得到滿足 A[i]+B[j]+C[k]+D[l]=0 的四元組數目。
class Solution:def fourSumCount(self, A: List[int], B: List[int], C: List[int], D: List[int]) -> int:dic = collections.Counter(u + v for u in A for v in B)return sum(dic.get(- c - d, 0) for c in C for d in D)
復雜度分析
-
時間復雜度:O(n^2)。
我們使用了兩次二重循環,時間復雜度均為 O(n^2)。在循環中對哈希映射進行的修改以及查詢操作的期望時間復雜度均為 O(1)。 -
空間復雜度:O(n^2),即為哈希映射需要使用的空間。
在最壞的情況下,A[i]+B[j] 的值均不相同,因此值的個數為 n^2,也就需要 O(n^2)的空間
關于Python中collections模塊
這個模塊實現了特定目標的容器,以提供Python標準內建容器 dict、list、set、tuple 的替代選擇。
- Counter:字典的子類,提供了哈希對象的計數功能
- defaultdict:字典的子類,提供了一個工廠函數,為字典查詢提供了默認值
- OrderedDict:字典的子類,保留了他們被添加的順序
- namedtuple:創建命名元組子類的工廠函數
- deque:類似列表容器,實現了在兩端快速添加(append)和彈出(pop)
- ChainMap:類似字典的容器類,將多個映射集合到一個視圖里面
總結
以上是生活随笔為你收集整理的【Leetcode】 刷题之路1(python)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 别克lacrosse2.8t后保险杠杠多
- 下一篇: 寻秦记是谁画的呢?