查找---结合力扣几道经典例题讲解
文章目錄
- 一.查找表
- 考慮的基本數據結構
- 算法應用
- LeetCode 349 Intersection Of Two Arrays 1
- 題目描述
- 分析實現
- LeetCode 350 Intersection Of Two Arrays 2
- 題目描述
- 分析實現
- LeetCode 242 Intersection Of Two Arrays 2
- 題目描述
- 分析實現
- LeetCode 202 Happy number
- 題目描述
- 分析實現
- tips
- LeetCode 290 Word Pattern
- 題目描述
- 分析實現
- tips
- LeetCode 205 Isomorphic Strings
- 題目描述
- 分析實現
- LeetCode 451 Sort Characters By Frequency
- 題目描述
- 分析實現
- tips
- 二. 對撞指針
- LeetCode 1 Two Sum
- 題目描述
- 審題:
- 分析實現
- 暴力法O(n^2)
- 排序+指針對撞(O(n)+O(nlogn)=O(n))
- 小套路:
- 更加pythonic的實現
- 查找表--O(n)
- 補充思路:
- LeetCode 15 3Sum
- 題目描述
- 審題
- 分析實現
- 沒有考慮重復元素導致錯誤
- 代碼實現
- 小套路
- LeetCode 18 4Sum
- 題目描述
- 題目分析
- 超出時間限制
- LeetCode 16 3Sum Closest
- 題目描述
- 分析實現
- 偽代碼
- 3Sum問題兩層遍歷得套路代碼:
- 代碼實現:
- LeetCode 454 4SumⅡ
- 題目描述
- 分析實現
- O(n^3)代碼
- O(n^2)級代碼
- LeetCode 49 Group Anagrams
- 題目描述
- 分析實現
- 錯誤思路
- LeetCode 447 Number of Boomerangs
- 題目描述
- 分析實現
- 原始思路
- 查找表
- 距離
- 優化
- LeetCode 149 Max Points on a Line
- 題目描述
- 分析實現
- 總結
- 三. 滑動數組
- LeetCode 219 Contains Dupliccate Ⅱ
- 題目描述
- 分析實現
- LeetCode 220 Contains Dupliccate Ⅲ
- 題目描述
- 分析實現
- 小套路:
- 四. 二分查找
- 理解
- 代碼模板
- LeetCode 35. Search Insert Position
- LeetCode540. Single Element in a Sorted Array
- LeetCode 410. Split Array Largest Sum
- 感謝Datawhale精心準備的這次學習資料,讓我受益頗多。
一.查找表
考慮的基本數據結構
第一類: 查找有無–set
元素’a’是否存在,通常用set:集合
set只存儲鍵,而不需要對應其相應的值。
set中的鍵不允許重復
第二類: 查找對應關系(鍵值對應)–dict
元素’a’出現了幾次:dict–>字典
dict中的鍵不允許重復
第三類: 改變映射關系–map
通過將原有序列的關系映射統一表示為其他
算法應用
LeetCode 349 Intersection Of Two Arrays 1
題目描述
給定兩個數組nums,求兩個數組的公共元素。
如nums1 = [1,2,2,1],nums2 = [2,2]結果為[2] 結果中每個元素只能出現一次 出現的順序可以是任意的分析實現
由于每個元素只出現一次,因此不需要關注每個元素出現的次數,用set的數據結構就可以了。記錄元素的有和無。
把nums1記錄為set,判斷nums2的元素是否在set中,是的話,就放在一個公共的set中,最后公共的set就是我們要的結果。
代碼如下:
class Solution:def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:nums1 = set(nums1)return set([i for i in nums2 if i in nums1])也可以通過set的內置方法來實現,直接求set的交集:
class Solution:def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:set1 = set(nums1)set2 = set(nums2)return set2 & set1LeetCode 350 Intersection Of Two Arrays 2
題目描述
給定兩個數組nums,求兩個數組的交集。
– 如nums1=[1,2,2,1],nums=[2,2]
– 結果為[2,2]
– 出現的順序可以是任意的
分析實現
元素出現的次數有用,那么對于存儲次數就是有意義的,所以選擇數據結構時,就應該選擇dict的結構,通過字典的比較來判斷;
記錄每個元素的同時要記錄這個元素的頻次。
記錄num1的字典,遍歷nums2,比較nums1的字典的nums的key是否大于零,從而進行判斷。
代碼如下:
class Solution:def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:from collections import Counternums1_dict = Counter(nums1)res = []for num in nums2:if nums1_dict[num] > 0:# 說明找到了一個元素即在num1也在nums2res.append(num)nums1_dict[num] -= 1return resLeetCode 242 Intersection Of Two Arrays 2
題目描述
給定兩個字符串 s 和 t ,編寫一個函數來判斷 t 是否是 s 的字母異位詞。
示例1:輸入: s = "anagram", t = "nagaram" 輸出: true示例 2:輸入: s = "rat", t = "car" 輸出: false分析實現
判斷異位詞即判斷變換位置后的字符串和原來是否相同,那么不僅需要存儲元素,還需要記錄元素的個數。可以選擇dict的數據結構,將字符串s和t都用dict存儲,而后直接比較兩個dict是否相同。
class Solution:def isAnagram(self, s: str, t: str) -> bool:from collections import Counters = Counter(s)t = Counter(t)if s == t:return Trueelse:return FalseLeetCode 202 Happy number
題目描述
編寫一個算法來判斷一個數是不是“快樂數”。
一個“快樂數”定義為:對于一個正整數,每一次將該數替換為它每個位置上的數字的平方和,然后重復這個過程直到這個數變為 1,也可能是無限循環但始終變不到 1。如果可以變為 1,那么這個數就是快樂數。
示例: 輸入: 19 輸出: true 解釋: 1^2 + 9^2 = 82 8^2 + 2^2 = 68 6^2 + 8^2 = 100 1^2 + 0^2 + 0^2 = 1分析實現
這道題目思路很明顯,當n不等于1時就循環,每次循環時,將其最后一位到第一位的數依次平方求和,比較求和是否為1。
難點在于,什么時候跳出循環?
開始筆者的思路是,循環個100次,還沒得出結果就false,但是小學在算無限循環小數時有一個特征,就是當除的數中,和之前歷史的得到的數有重合時,這時就是無限循環小數。
那么這里也可以按此判斷,因為只需要判斷有或無,不需要記錄次數,故用set的數據結構。每次對求和的數進行append,當新一次求和的值存在于set中時,就return false.
代碼如下:
class Solution:def isHappy(self, n: int) -> bool:already = set()while n != 1:sum = 0while n > 0:# 取n的最后一位數tmp = n % 10 sum += tmp ** 2# 將n的最后一位截掉n //= 10# 如果求的和在過程中出現過if sum in already:return Falseelse:already.add(sum)n = sumreturn Truetips
#一般對多位數計算的套路是: #循環從后向前取位數 while n >0 : #取最后一位: tmp = n % 10 #再截掉最后一位: n = n // 10LeetCode 290 Word Pattern
題目描述
給出一個模式(pattern)以及一個字符串,判斷這個字符串是否符合模式
示例1: 輸入: pattern = "abba", str = "dog cat cat dog" 輸出: true示例 2: 輸入:pattern = "abba", str = "dog cat cat fish" 輸出: false示例 3: 輸入: pattern = "aaaa", str = "dog cat cat dog" 輸出: false示例 4: 輸入: pattern = "abba", str = "dog dog dog dog" 輸出: false分析實現
抓住變與不變,筆者開始的思路是選擇了dict的數據結構,比較count值和dict對應的keys的個數是否相同,但是這樣無法判斷順序的關系,如測試用例:‘aba’,‘cat cat dog’。
那么如何能既考慮順序,也考慮鍵值對應的關系呢?
抓住變與不變,變的是鍵,但是不變的是各個字典中,對應的相同index下的值,如dict1[index] = dict2[index],那么我們可以創建兩個新的字典,遍歷index對兩個新的字典賦值,并比較value。
還有一個思路比較巧妙,既然不同,那么可以考慮怎么讓它們相同,將原來的dict通過map映射為相同的key,再比較相同key的dict是否相同。
代碼實現如下:
class Solution:def wordPattern(self,pattern, str):str = str.split()return list(map(pattern.index,pattern)) == list(map(str.index,str))tips
LeetCode 205 Isomorphic Strings
題目描述
給定兩個字符串 s 和 t,判斷它們是否是同構的。
如果 s 中的字符可以被替換得到 t ,那么這兩個字符串是同構的。
所有出現的字符都必須用另一個字符替換,同時保留字符的順序。兩個字符不能映射到同一個字符上,但字符可以映射自己本身。
示例 1: 輸入: s = "egg", t = "add" 輸出: true示例 2: 輸入: s = "foo", t = "bar" 輸出: false示例 3: 輸入: s = "paper", t = "title" 輸出: true分析實現
思路與上題一致,可以考慮通過建兩個dict,比較怎樣不同,也可以將不同轉化為相同。
直接用上題的套路代碼:
class Solution:def isIsomorphic(self, s: str, t: str) -> bool:return list(map(s.index,s)) == list(map(t.index,t))LeetCode 451 Sort Characters By Frequency
題目描述
給定一個字符串,請將字符串里的字符按照出現的頻率降序排列。
示例 1: 輸入: "tree" 輸出: "eert"示例 2: 輸入: "cccaaa" 輸出: "cccaaa"示例 3: 輸入: "Aabb" 輸出: "bbAa"分析實現
對于相同頻次的字母,順序任意,需要考慮大小寫,返回的是字符串。
使用字典統計頻率,對字典的value進行排序,最終根據key的字符串乘上value次數,組合在一起輸出。
class Solution:def frequencySort(self, s: str) -> str:from collections import Counters_dict = Counter(s)# sorted返回的是列表元組s = sorted(s_dict.items(), key=lambda item:item[1], reverse = True)# 因為返回的是字符串res = ''for key, value in s:res += key * value return restips
二. 對撞指針
LeetCode 1 Two Sum
題目描述
給出一個整型數組nums,返回這個數組中兩個數字的索引值i和j,使得nums[i] + nums[j]等于一個給定的target值,兩個索引不能相等。
如:nums= [2,7,11,15],target=9
返回[0,1]
審題:
需要考慮:
分析實現
暴力法O(n^2)
時間復雜度為O(n^2),第一遍遍歷數組,第二遍遍歷當前遍歷值之后的元素,其和等于target則return。
class Solution:def twoSum(self, nums: List[int], target: int) -> List[int]:len_nums = len(nums)for i in range(len_nums):for j in range(i+1,len_nums):if nums[i] + nums[j] == target:return [i,j]排序+指針對撞(O(n)+O(nlogn)=O(n))
在數組篇的LeetCode 167題中,也遇到了找到兩個數使得它們相加之和等于目標數,但那是對于排序的情況,因此也可以使用上述的思路來完成。
因為問題本身不是有序的,因此需要對原來的數組進行一次排序,排序后就可以用O(n)的指針對撞進行解決。
但是問題是,返回的是數字的索引,如果只是對數組的值進行排序,那么數組原來表示的索引的信息就會丟失,所以在排序前要進行些處理。
錯誤代碼示例–只使用dict來進行保存:
class Solution:def twoSum(self, nums: List[int], target: int) -> List[int]:record = dict()for index in range(len(nums)):record[nums[index]] = index nums.sort()l,r = 0,len(nums)-1while l < r:if nums[l] + nums[r] == target:return [record[nums[l]],record[nums[r]]]elif nums[l] + nums[r] < target:l += 1else:r -= 1當遇到相同的元素的索引問題時,會不滿足條件:
如:[3,3] 6
在排序前先使用一個額外的數組拷貝一份原來的數組,對于兩個相同元素的索引問題,使用一個bool型變量輔助將兩個索引都找到,總的時間復雜度為O(n)+O(nlogn) = O(nlogn)
class Solution:def twoSum(self, nums: List[int], target: int) -> List[int]:record = dict()nums_copy = nums.copy()sameFlag = True;nums.sort()l,r = 0,len(nums)-1while l < r:if nums[l] + nums[r] == target:breakelif nums[l] + nums[r] < target:l += 1else:r -= 1res = []for i in range(len(nums)):if nums_copy[i] == nums[l] and sameFlag:res.append(i)sameFlag = Falseelif nums_copy[i] == nums[r]:res.append(i)return res小套路:
如果只是對數組的值進行排序,那么數組原來表示的索引的信息就會丟失的情況,可以在排序前:
更加pythonic的實現
通過list(enumerate(nums))開始實現下標和值的綁定,不用專門的再copy加bool判斷。
nums = list(enumerate(nums)) nums.sort(key = lambda x:x[1]) i,j = 0, len(nums)-1 while i < j:if nums[i][1] + nums[j][1] > target:j -= 1elif nums[i][1] + nums[j][1] < target:i += 1else:if nums[j][0] < nums[i][0]:nums[j],nums[i] = nums[i],nums[j]return num[i][0],nums[j][0]拷貝數組 + bool型變量輔助
查找表–O(n)
遍歷數組過程中,當遍歷到元素v時,可以只看v前面的元素,是否含有target-v的元素存在。
即使v放在了之前的查找表中覆蓋了v,也不影響當前v元素的查找。因為只需要找到兩個元素,只需要找target-v的另一個元素即可。
class Solution:def twoSum(self, nums: List[int], target: int) -> List[int]:record = dict()for i in range(len(nums)):complement = target - nums[i]# 已經在之前的字典中找到這個值if record.get(complement) is not None:res = [i,record[complement]]return resrecord[nums[i]] = i只進行一次循環,故時間復雜度O(n),空間復雜度為O(n)
補充思路:
通過enumerate來把索引和值進行綁定,進而對value進行sort,前后對撞指針進行返回。
class Solution:def twoSum(self, nums: List[int], target: int) -> List[int]:nums = list(enumerate(nums))# 根據value來排序nums.sort(key = lambda x:x[1])l,r = 0, len(nums)-1while l < r:if nums[l][1] + nums[r][1] == target:return nums[l][0],nums[r][0]elif nums[l][1] + nums[r][1] < target:l += 1else:r -= 1LeetCode 15 3Sum
題目描述
給出一個整型數組,尋找其中的所有不同的三元組(a,b,c),使得a+b+c=0
注意:答案中不可以包含重復的三元組。
如:nums = [-1, 0, 1, 2, -1, -4],
結果為:[[-1, 0, 1],[-1, -1, 2]]
審題
分析實現
因為上篇中已經實現了Two Sum的問題,因此對于3Sum,首先想到的思路就是,開始固定一個k,然后在其后都當成two sum問題來進行解決,但是這樣就ok了嗎?
沒有考慮重復元素導致錯誤
直接使用Two Sum問題中的查找表的解法,根據第一層遍歷的i,將i之后的數組作為two sum問題進行解決。
class Solution:def threeSum(self, nums: [int]) -> [[int]]:res = []for i in range(len(nums)):num = 0 - nums[i]record = dict()for j in range(i + 1, len(nums)):complement = num - nums[j]# 已經在之前的字典中找到這個值if record.get(complement) is not None:res_lis = [nums[i], nums[j], complement]res.append(res_lis)record[nums[j]] = ireturn res但是這樣會導致一個錯誤,錯誤用例如下:
輸入: [-1,0,1,2,-1,-4] 輸出: [[-1,1,0],[-1,-1,2],[0,-1,1]] 預期結果: [[-1,-1,2],[-1,0,1]]代碼在實現的過程中沒有把第一次遍歷的i的索引指向相同元素的情況排除掉,于是出現了當i指針后面位置的元素有和之前訪問過的相同的值,于是重復遍歷。
那么可以考慮,開始時對nums數組進行排序,排序后,當第一次遍歷的指針k遇到下一個和前一個指向的值重復時,就將其跳過。為了方便計算,在第二層循環中,可以使用對撞指針的套路:
# 對撞指針套路 l,r = 0, len(nums)-1 while l < r:if nums[l] + nums[r] == target:return nums[l],nums[r]elif nums[l] + nums[r] < target:l += 1else:r -= 1其中需要注意的是,在里層循環中,也要考慮重復值的情況,因此當值相等時,再次移動指針時,需要保證其指向的值和前一次指向的值不重復,因此可以:
# 對撞指針套路 l,r = 0, len(nums)-1 while l < r:sum = nums[i] + nums[l] + nums[r]if sum == target:res.append([nums[i],nums[l],nums[r])l += 1r -= 1while l < r and nums[l] == nums[l-1]: l += 1while l < r and nums[r] == nums[r+1]: r -= 1elif sum < target:l += 1else:r -= 1再調整下遍歷的范圍,因為設了3個索引:i,l,r。邊界情況下,r索引指向len-1, l指向len-2,索引i遍歷的邊界為len-3,故for循環是從0到len-2。
代碼實現如下:
代碼實現
class Solution:def threeSum(self, nums: [int]) -> [[int]]:nums.sort()res = []for i in range(len(nums)-2):# 因為是排序好的數組,如果最小的都大于0可以直接排除if nums[i] > 0: break# 排除i的重復值if i > 0 and nums[i] == nums[i-1]: continuel,r = i+1, len(nums)-1while l < r:sum = nums[i] + nums[l] + nums[r]if sum == 0:res.append([nums[i],nums[l],nums[r]])l += 1r -= 1while l < r and nums[l] == nums[l-1]: l += 1while l < r and nums[r] == nums[r+1]: r -= 1elif sum < 0:l += 1else:r -= 1return res小套路
LeetCode 18 4Sum
題目描述
給出一個整形數組,尋找其中的所有不同的四元組(a,b,c,d),使得a+b+c+d等于一個給定的數字target。
如: nums = [1, 0, -1, 0, -2, 2],target = 0結果為: [[-1, 0, 0, 1],[-2, -1, 1, 2],[-2, 0, 0, 2]]題目分析
4Sum可以當作是3Sum問題的擴展,注意事項仍是一樣的,同樣是不能返回重復值得解。首先排序。接著從[0,len-1]遍歷i,跳過i的重復元素,再在[i+1,len-1]中遍歷j,得到i,j后,再選擇首尾的l和r,通過對撞指針的思路,四數和大的話r–,小的話l++,相等的話納入結果list,最后返回。
套用3Sum得代碼,在其前加一層循環,對邊界情況進行改動即可:
代碼實現如下:
class Solution:def fourSum(self, nums: List[int], target: int) -> List[List[int]]:nums.sort()res = []if len(nums) < 4: return resif len(nums) == 4 and sum(nums) == target:res.append(nums)return resfor i in range(len(nums)-3):if i > 0 and nums[i] == nums[i-1]: continuefor j in range(i+1,len(nums)-2):if j > i+1 and nums[j] == nums[j-1]: continuel,r = j+1, len(nums)-1while l < r:sum_value = nums[i] + nums[j] + nums[l] + nums[r]if sum_value == target:res.append([nums[i],nums[j],nums[l],nums[r]])l += 1r -= 1while l < r and nums[l] == nums[l-1]: l += 1while l < r and nums[r] == nums[r+1]: r -= 1elif sum_value < target:l += 1else:r -= 1return res還可以使用combinations(nums, 4)來對原數組中得4個元素全排列,在開始sort后,對排列得到得元素進行set去重。但單純利用combinations實現會超時。
超出時間限制
class Solution:def fourSum(self, nums: List[int], target: int) -> List[List[int]]:nums.sort()from itertools import combinationsres = []for i in combinations(nums, 4):if sum(i) == target:res.append(i)res = set(res)return resLeetCode 16 3Sum Closest
題目描述
給出一個整形數組,尋找其中的三個元素a,b,c,使得a+b+c的值最接近另外一個給定的數字target。
如:給定數組 nums = [-1,2,1,-4], 和 target = 1.
與 target 最接近的三個數的和為 2. (-1 + 2 + 1 = 2).
分析實現
這道題也是2sum,3sum等題組中的,只不過變形的地方在于不是找相等的target,而是找最近的。
那么開始時可以隨機設定一個三個數的和為結果值,在每次比較中,先判斷三個數的和是否和target相等,如果相等直接返回和。如果不相等,則判斷三個數的和與target的差是否小于這個結果值時,如果小于則進行則進行替換,并保存和的結果值。
偽代碼
# 先排序 nums.sort() # 隨機選擇一個和作為結果值 res = nums[0] + nums[1] + nums[2] # 記錄這個差值 diff = abs(nums[0]+nums[1]+nums[2]-target) # 第一遍遍歷 for i in range(len(nums)):# 標記好剩余元素的l和rl,r = i+1, len(nums-1)while l < r:if 后續的值等于target:return 三個數值得和else:if 差值小于diff:更新diff值更新res值if 和小于target:將l移動else:(開始已經排除了等于得情況,要判斷和大于target)將r移動3Sum問題兩層遍歷得套路代碼:
nums.sort() res = [] for i in range(len(nums)-2):l,r = i+1, len(nums)-1while l < r:sum = nums[i] + nums[l] + nums[r]if sum == 0:res.append([nums[i],nums[l],nums[r]])elif sum < 0:l += 1else:r -= 1代碼實現:
class Solution:def threeSumClosest(self, nums: List[int], target: int) -> int:nums.sort()diff = abs(nums[0]+nums[1]+nums[2]-target)res = nums[0] + nums[1] + nums[2]for i in range(len(nums)):l,r = i+1,len(nums)-1t = target - nums[i]while l < r:if nums[l] + nums[r] == t:return nums[i] + telse:if abs(nums[l]+nums[r]-t) < diff:diff = abs(nums[l]+nums[r]-t)res = nums[i]+nums[l]+nums[r]if nums[l]+nums[r] < t:l += 1else:r -= 1return res時間復雜度為O(n^2),空間復雜度為O(1);
LeetCode 454 4SumⅡ
題目描述
給出四個整形數組A,B,C,D,尋找有多少i,j,k,l的組合,使得A[i]+B[j]+C[k]+D[l]=0。其中,A,B,C,D中均含有相同的元素個數N,且0<=N<=500;
輸入:
A = [ 1, 2]
B = [-2,-1]
C = [-1, 2]
D = [ 0, 2]
輸出:2
分析實現
這個問題同樣是Sum類問題得變種,其將同一個數組的條件,變為了四個數組中,依然可以用查找表的思想來實現。
首先可以考慮把D數組中的元素都放入查找表,然后遍歷前三個數組,判斷target減去每個元素后的值是否在查找表中存在,存在的話,把結果值加1。那么查找表的數據結構選擇用set還是dict?考慮到數組中可能存在重復的元素,而重復的元素屬于不同的情況,因此用dict存儲,最后的結果值加上dict相應key的value,代碼如下:
O(n^3)代碼
from collections import Counter record = Counter() # 先建立數組D的查找表 for i in range(len(D)):record[D[i]] += 1 res = 0 for i in range(len(A)):for j in range(len(B)):for k in range(len(C)):num_find = 0-A[i]-B[j]-C[k]if record.get(num_find) != None:res += record(num_find) return res但是對于題目中給出的數據規模:N<=500,如果N為500時,n^3的算法依然消耗很大,能否再進行優化呢?
根據之前的思路繼續往前走,如果只遍歷兩個數組,那么就可以得到O(n^2)級別的算法,但是遍歷兩個數組,那么還剩下C和D兩個數組,上面的值怎么放?
對于查找表問題而言,很多時候到底要查找什么,是解決的關鍵。對于C和D的數組,可以通過dict來記錄其中和的個數,之后遍歷結果在和中進行查找。代碼如下:
O(n^2)級代碼
class Solution:def fourSumCount(self, A: List[int], B: List[int], C: List[int], D: List[int]) -> int:from collections import Counterrecord = Counter()for i in range(len(A)):for j in range(len(B)):record[A[i]+B[j]] += 1res = 0for i in range(len(C)):for j in range(len(D)):find_num = 0 - C[i] - D[j]if record.get(find_num) != None:res += record[find_num]return res再使用Pythonic的列表生成式和sum函數進行優化,如下:
class Solution:def fourSumCount(self, A: List[int], B: List[int], C: List[int], D: List[int]) -> int:record = collections.Counter(a + b for a in A for b in B)return sum(record.get(- c - d, 0) for c in C for d in D)LeetCode 49 Group Anagrams
題目描述
給出一個字符串數組,將其中所有可以通過顛倒字符順序產生相同結果的單詞進行分組。
示例: 輸入: ["eat", "tea", "tan", "ate", "nat", "bat"], 輸出:[["ate","eat","tea"],["nat","tan"],["bat"]]說明: 所有輸入均為小寫字母。 不考慮答案輸出的順序。分析實現
在之前LeetCode 242的問題中,對字符串t和s來判斷,判斷t是否是s的字母異位詞。當時的方法是通過構建t和s的字典,比較字典是否相同來判斷是否為異位詞。
在剛開始解決這個問題時,我也局限于了這個思路,以為是通過移動指針,來依次比較兩個字符串是否對應的字典相等,進而確定異位詞列表,再把異位詞列表添加到結果集res中。于是有:
錯誤思路
nums = ["eat", "tea", "tan", "ate", "nat", "bat"]from collections import Counter cum = [] for i in range(len(nums)):l,r = i+1,len(nums)-1i_dict = Counter(nums[i])res = []if nums[i] not in cum:res.append(nums[i])while l < r:l_dict = Counter(nums[l])r_dict = Counter(nums[r])if i_dict == l_dict and l_dict == r_dict:res.append(nums[l],nums[r])l += 1r -= 1elif i_dict == l_dict:res.append(nums[l])l += 1elif i_dict == r_dict:res.append(nums[r])r -= 1else:l += 1print(res)cum.append(res) ......................................這時發現長長綿綿考慮不完,而且還要注意指針的條件,怎樣遍歷才能遍歷所有的情況且判斷列表是否相互間包含。。。
于是立即開始反思是否哪塊考慮錯了?回顧第一開始的選擇數據結構,在dict和list中,自己錯誤的選擇了list來當作數據結構,進而用指針移動來判斷元素的情況。而沒有利用題目中不變的條件。
題目的意思,對異位詞的進行分組,同異位詞的分為一組,那么考慮對這一組內什么是相同的,且這個相同的也能作為不同組的判斷條件。
不同組的判斷條件,就可以用數據結構dict中的key來代表,那么什么相同的適合當作key呢?
這時回顧下下LeetCode 242,當時是因為異位字符串中包含的字符串的字母個數都是相同的,故把字母當作key來進行判斷是否為異位詞。
但是對于本題,把每個字符串的字母dict,再當作字符串數組的dict的key,顯然不太合適,那么對于異位詞,還有什么是相同的?
顯然,如果將字符串統一排序,異位詞排序后的字符串,顯然都是相同的。那么就可以把其當作key,把遍歷的數組中的異位詞當作value,對字典進行賦值,進而遍歷字典的value,得到結果list。
需要注意的細節是,字符串和list之間的轉換:
再將能用列表生成式替換的地方替換掉,代碼實現如下:
class Solution:def groupAnagrams(self, strs: List[str]) -> List[List[str]]:from collections import defaultdictstrs_dict = defaultdict(list)for str in strs:key = ''.join(sorted(list(str)))strs_dict[key] += str.split(',')return [v for v in strs_dict.values()]LeetCode 447 Number of Boomerangs
題目描述
給出一個平面上的n個點,尋找存在多少個由這些點構成的三元組(i,j,k),使得i,j兩點的距離等于i,k兩點的距離。
其中n最多為500,且所有的點坐標的范圍在[-10000,10000]之間。
輸入: [[0,0],[1,0],[2,0]]輸出: 2 解釋: 兩個結果為: [[1,0],[0,0],[2,0]] 和 [[1,0],[2,0],[0,0]]分析實現
原始思路
題目的要求是:使得i,j兩點的距離等于i,k兩點的距離,那么相當于是比較三個點之間距離的,那么開始的思路就是三層遍歷,i從0到len,j從i+1到len,k從j+1到len,然后比較三個點的距離,相等則結果數加一。
顯然這樣的時間復雜度為O(n^3),對于這道題目,能否用查找表的思路進行解決優化?
查找表
之前的查找表問題,大多是通過構建一個查找表,而避免了在查找中再內層嵌套循環,從而降低了時間復雜度。那么可以考慮在這道題中,可以通過查找表進行代替哪兩層循環。
當i,j兩點距離等于i,k時,用查找表的思路,等價于:對距離key(i,j或i,k的距離),其值value(個數)為2。
那么就可以做一個查找表,用來查找相同距離key的個數value是多少。遍歷每一個節點i,掃描得到其他點到節點i的距離,在查找表中,對應的鍵就是距離的值,對應的值就是距離值得個數。
在拿到對于元素i的距離查找表后,接下來就是排列選擇問題了:
距離
對于距離值的求算,按照歐式距離的方法進行求算的話,容易產生浮點數,可以將根號去掉,用差的平方和來進行比較距離。
實現代碼如下:
class Solution:def numberOfBoomerangs(self, points: List[List[int]]) -> int:res = 0from collections import Counterfor i in points:record = Counter()for j in points:if i != j:record[self.dis(i,j)] += 1for k,v in record.items():res += v*(v-1)return resdef dis(self,point1,point2):return (point1[0]-point2[0]) ** 2 + (point1[1]-point2[1]) ** 2優化
對實現的代碼進行優化:
LeetCode 149 Max Points on a Line
題目描述
給定一個二維平面,平面上有 n 個點,求最多有多少個點在同一條直線上。
示例 1: 輸入: [[1,1],[2,2],[3,3]] 輸出: 3示例 2: 輸入: [[1,1],[3,2],[5,3],[4,1],[2,3],[1,4]] 輸出: 4分析實現
本道題目的要求是:看有多少個點在同一條直線上,那么判斷點是否在一條直線上,其實就等價于判斷i,j兩點的斜率是否等于i,k兩點的斜率。
回顧上道447題目中的要求:使得i,j兩點的距離等于i,k兩點的距離,那么在這里,直接考慮使用查找表實現,即查找相同斜率key的個數value是多少。
在上個問題中,i和j,j和i算是兩種不同的情況,但是這道題目中,這是屬于相同的兩個點,
因此在對遍歷每個i,查找與i相同斜率的點時,不能再對結果數res++,而應該取查找表中的最大值。如果有兩個斜率相同時,返回的應該是3個點,故返回的是結果數+1。
查找表實現套路如下:
class Solution:def maxPoints(self,points):res = 0from collections import defaultdictfor i in range(len(points)):record = defaultdict(int)for j in range(len(points)):if i != j:record[self.get_Slope(points,i,j)] += 1for v in record.values():res = max(res, v)return res + 1def get_Slope(self,points,i,j):return (points[i][0] - points[j][0]) / (points[i][1] - points[j][1])但是這樣會出現一個問題,即斜率的求算中,有時會出現直線為垂直的情況,故需要對返回的結果進行判斷,如果分母為0,則返回inf,如下:
def get_Slope(self,points,i,j):if points[i][1] - points[j][1] == 0:return float('Inf')else:return (points[i][0] - points[j][0]) / (points[i][1] - points[j][1])再次提交,發現對于空列表的測試用例會判斷錯誤,于是對邊界情況進行判斷,如果初始長度小于等于1,則直接返回len:
if len(points) <= 1:return len(points)再次提交,對于相同元素的測試用例會出現錯誤,回想剛才的過程,當有相同元素時,題目的要求是算作兩個不同的點,但是在程序運行時,會將其考慮為相同的點,return回了inf。但在實際運行時,需要對相同元素的情況單獨考慮。
于是可以設定samepoint值,遍歷時判斷,如果相同時,same值++,最后取v+same的值作為結果數。
考慮到如果全是相同值,那么這時dict中的record為空,也要將same值當作結果數返回,代碼實現如下:
class Solution:def maxPoints(self,points):if len(points) <= 1:return len(points)res = 0from collections import defaultdictfor i in range(len(points)):record = defaultdict(int)samepoint = 0for j in range(len(points)):if points[i][0] == points[j][0] and points[i][1] == points[j][1]:samepoint += 1else:record[self.get_Slope(points,i,j)] += 1for v in record.values():res = max(res, v+samepoint)res = max(res, samepoint)return resdef get_Slope(self,points,i,j):if points[i][1] - points[j][1] == 0:return float('Inf')else:return (points[i][0] - points[j][0]) / (points[i][1] - points[j][1])時間復雜度為O(n^2),空間復雜度為O(n)
總結
遍歷時多用索引,而不要直接用值進行遍歷;
三. 滑動數組
LeetCode 219 Contains Dupliccate Ⅱ
題目描述
給出一個整形數組nums和一個整數k,是否存在索引i和j,使得nums[i]==nums[j],且i和J之間的差不超過k。
示例1: 輸入: nums = [1,2,3,1], k = 3 輸出: true示例 2: 輸入: nums = [1,2,3,1,2,3], k = 2 輸出: false分析實現
翻譯下這個題目:在這個數組中,如果有兩個元素索引i和j,它們對應的元素是相等的,且索引j-i是小于等于k,那么就返回True,否則返回False。
因為對于這道題目可以用暴力解法雙層循環,即:
for i in range(len(nums)):for j in range(i+1,len(nums)):if i == j:return True return False故這道題目可以考慮使用滑動數組來解決:
固定滑動數組的長度為K+1,當這個滑動數組內如果能找到兩個元素的值相等,就可以保證兩個元素的索引的差是小于等于k的。如果當前的滑動數組中沒有元素相同,就右移滑動數組的右邊界r,同時將左邊界l右移。查看r++的元素是否在l右移過后的數組里,如果不在就將其添加數組,在的話返回true表示兩元素相等。
因為滑動數組中的元素是不同的,考慮用set作為數據結構:
class Solution:def containsNearbyDuplicate(self, nums: List[int], k: int) -> bool:record = set()for i in range(len(nums)):if nums[i] in record:return Truerecord.add(nums[i])if len(record) == k+1:record.remove(nums[i-k])return False時間復雜度為O(n),空間復雜度為O(n)
LeetCode 220 Contains Dupliccate Ⅲ
題目描述
給定一個整數數組,判斷數組中是否有兩個不同的索引 i 和 j,使得nums [i] 和nums [j]的差的絕對值最大為 t,并且 i 和 j 之間的差的絕對值最大為 ?。
示例 1:
輸入: nums = [1,2,3,1], k = 3, t = 0
輸出: true
示例 2:
輸入: nums = [1,0,1,1], k = 1, t = 2
輸出: true
示例 3:
輸入: nums = [1,5,9,1,5,9], k = 2, t = 3
輸出: false
分析實現
相比較上一個問題,這個問題多了一個限定條件,條件不僅索引差限定k,數值差也限定為了t。
將索引的差值固定,于是問題和上道一樣,同樣轉化為了固定長度K+1的滑動窗口內,是否存在兩個值的差距不超過 t,考慮使用滑動窗口的思想來解決。
在遍歷的過程中,目的是要在“已經出現、但還未滑出滑動窗口”的所有數中查找,是否有一個數與滑動數組中的數的差的絕對值最大為 t。對于差的絕對值最大為t,實際上等價于所要找的這個元素v的范圍是在v-t到v+t之間,即查找“滑動數組”中的元素有沒有[v-t,v+t]范圍內的數存在。
因為只需證明是否存在即可,這時判斷的邏輯是:如果在滑動數組查找比v-t大的最小的元素,如果這個元素小于等于v+t,即可以證明存在[v-t,v+t]。
那么實現過程其實和上題是一致的,只是上題中的判斷條件是在查找表中找到和nums[i]相同的元素,而這題中的判斷條件是查找比v-t大的最小的元素,判斷其小于等于v+t,下面是實現的框架:
class Solution:def containsNearbyDuplicate(self, nums: List[int], k: int) -> bool:record = set()for i in range(len(nums)):if 查找的比v-t大的最小的元素 <= v+t:return Truerecord.add(nums[i])if len(record) == k+1:record.remove(nums[i-k])return False接下來考慮,如何查找比v-t大的最小的元素呢?
【注:C++中有lower_bound(v-t)的實現,py需要自己寫函數】
當然首先考慮可以通過O(n)的解法來完成,如下:
def lower_bound(self,array,v):array = list(array)for i in range(len(array)):if array[i] >= v:return ireturn -1但是滑動數組作為set,是有序的數組。對于有序的數組,應該第一反應就是二分查找,于是考慮二分查找實現,查找比v-t大的最小的元素:
def lower_bound(self, nums, target):low, high = 0, len(nums)-1while low<high:mid = int((low+high)/2)if nums[mid] < target:low = mid+1else:high = midreturn low if nums[low] >= target else -1整體代碼實現如下,時間復雜度為O(nlogn),空間復雜度為O(n):
class Solution:def containsNearbyAlmostDuplicate(self, nums, k, t) -> bool:record = set()for i in range(len(nums)):if len(record) != 0:rec = list(record)find_index = self.lower_bound(rec,nums[i]-t)if find_index != -1 and rec[find_index] <= nums[i] + t:return Truerecord.add(nums[i])if len(record) == k + 1:record.remove(nums[i - k])return Falsedef lower_bound(self, nums, target):low, high = 0, len(nums)-1while low<high:mid = int((low+high)/2)if nums[mid] < target:low = mid+1else:high = midreturn low if nums[low] >= target else -1當然。。。在和小伙伴一起刷的時候,這樣寫的O(n2)的結果會比上面要高,討論的原因應該是上面的步驟存在著大量set和list的轉換導致,對于py,仍舊是考慮算法思想實現為主,下面是O(n2)的代碼:
class Solution:def containsNearbyAlmostDuplicate(self, nums: List[int], k: int, t: int) -> bool:if t == 0 and len(nums) == len(set(nums)):return Falsefor i in range(len(nums)):for j in range(1,k+1):if i+j >= len(nums): breakif abs(nums[i+j]-nums[i]) <= t: return Truereturn False小套路:
二分查找實現,查找比v-t大的最小的元素:
def lower_bound(self, nums, target):low, high = 0, len(nums)-1while low<high:mid = int((low+high)/2)if nums[mid] < target:low = mid+1else:high = midreturn low if nums[low] >= target else -1二分查找實現,查找比v-t大的最小的元素:
def upper_bound(nums, target):low, high = 0, len(nums)-1while low<high:mid=(low+high)/2if nums[mid]<=target:low = mid+1else:#>high = midpos = highif nums[low]>target:pos = lowreturn -1四. 二分查找
理解
查找在算法題中是很常見的,但是怎么最大化查找的效率和寫出bugfree的代碼才是難的部分。一般查找方法有順序查找、二分查找和雙指針,推薦一開始可以直接用順序查找,如果遇到TLE的情況再考慮剩下的兩種,畢竟AC是最重要的。
一般二分查找的對象是有序或者由有序部分變化的(可能暫時理解不了,看例題即可),但還存在一種可以運用的地方是按值二分查找,之后會介紹。
代碼模板
總體來說二分查找是比較簡單的算法,網上看到的寫法也很多,掌握一種就可以了。
以下是我的寫法,參考C++標準庫里的寫法。這種寫法比較好的點在于:
- 1.即使區間為空、答案不存在、有重復元素、搜索開/閉區間的上/下界也同樣適用
- 2.±1 的位置調整只出現了一次,而且最后返回lo還是hi都是對的,無需糾結
解釋:
- 第一點:lo和hi分別對應搜索的上界和下界,但不一定為0和arr最后一個元素的下標。
- 第二點:因為Python沒有溢出,int型不夠了會自動改成long int型,所以無需擔心。如果再苛求一點,可以把這一行改成
- 第三點:
比較重要的就是這個f(x),在帶入模板的情況下,寫對函數就完了。
那么我們一步一步地揭開二分查找的神秘面紗,首先來一道簡單的題。
LeetCode 35. Search Insert Position
給定排序數組和目標值,如果找到目標,則返回索引。如果不是,則返回按順序插入索引的位置的索引。 您可以假設數組中沒有重復項。
Example
Example 1: Input: [1,3,5,6], 5 Output: 2Example 2: Input: [1,3,5,6], 2 Output: 1Example 3: Input: [1,3,5,6], 7 Output: 4Example 4: Input: [1,3,5,6], 0 Output: 0分析: 這里要注意的點是 high 要設置為 len(nums) 的原因是像第三個例子會超出數組的最大值,所以要讓 lo 能到 這個下標。
class Solution:def searchInsert(self, nums: List[int], target: int) -> int: lo, hi = 0, len(nums)while lo < hi:mid = (lo + hi) // 2if nums[mid] < target:lo = mid + 1else:hi = midreturn loLeetCode540. Single Element in a Sorted Array
您將獲得一個僅由整數組成的排序數組,其中每個元素精確出現兩次,但一個元素僅出現一次。 找到只出現一次的單個元素。
Example
Example 1:Input: [1,1,2,3,3,4,4,8,8] Output: 2Example 2:Input: [3,3,7,7,10,11,11] Output: 10分析: 異或的巧妙應用!如果mid是偶數,那么和1異或的話,那么得到的是mid+1,如果mid是奇數,得到的是mid-1。如果相等的話,那么唯一的元素還在這之后,往后找就可以了。
class Solution:def singleNonDuplicate(self, nums):lo, hi = 0, len(nums) - 1while lo < hi:mid = (lo + hi) // 2if nums[mid] == nums[mid ^ 1]:lo = mid + 1else:hi = midreturn nums[lo]是不是還挺簡單哈哈,那我們來道HARD難度的題!
LeetCode 410. Split Array Largest Sum
給定一個由非負整數和整數m組成的數組,您可以將該數組拆分為m個非空連續子數組。編寫算法以最小化這m個子數組中的最大和。
Example
Input: nums = [7,2,5,10,8] m = 2Output: 18Explanation: There are four ways to split nums into two subarrays. The best way is to split it into [7,2,5] and [10,8], where the largest sum among the two subarrays is only 18.分析:
- 這其實就是二分查找里的按值二分了,可以看出這里的元素就無序了。但是我們的目標是找到一個合適的最小和,換個角度理解我們要找的值在最小值max(nums)和sum(nums)內,而這兩個值中間是連續的。是不是有點難理解,那么看代碼吧
- 輔助函數的作用是判斷當前的“最小和”的情況下,區間數是多少,來和m判斷
- 這里的下界是數組的最大值是因為如果比最大值小那么一個區間就裝不下,數組的上界是數組和因為區間最少是一個,沒必要擴大搜索的范圍
感謝Datawhale精心準備的這次學習資料,讓我受益頗多。
總結
以上是生活随笔為你收集整理的查找---结合力扣几道经典例题讲解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 递归整理及几个经典题目
- 下一篇: linux版本的qq怎么安装路径,Ubu