Leetcode 454. 四数相加 II
讲解前:
这道题让我有点懵,我并不能想出一个合适的方法去利用哈希表的空间换取时间
讲解后:
其实这道题在看完了卡哥的解法之后我其实自己在之前就想过这种方法,但是深知这样会有时间复杂度是O(n^2),我就想肯定通过不了以外会超出时间限制,所以这里教会了我以后无论自己想到的方法是否已经是最优解,也要先尝试自己code一下才可以
class Solution:
def fourSumCount(self, nums1: List[int], nums2: List[int], nums3: List[int], nums4: List[int]) -> int:
# create the dict for first two list and the result
dict_12 = {}
res = 0
# iterate through first two list to add each possilbe sum and count to dict
for n1 in nums1:
for n2 in nums2:
s = n1 + n2
dict_12[s] = dict_12.get(s, 0) + 1
# check for list 3 and 4 to see the sum they need and look for
# it in the dict, once found, add the count to the res
for n3 in nums3:
for n4 in nums4:
need = 0 - (n3 + n4)
res = res + dict_12.get(need, 0)
return res
其实方法本身一点都不复杂,我们可能一开始想到的就是暴力解法直接找到所有的可能元组组合,在四个数组中,那样会出现一个时间复杂度是O(n^4),然后呢我们把问题简化,把原本的四个数组现在分成两部分,先找12和34这两个部分分别能够有多少种组合的sum,然后把12中的组合先存放在一个字典里面,key是sum的值然后value是这个sum出现了个次数,这样我们只需要再去遍历3和4然后算出他们的组合的sum之后,用0减去然后去找需要的这个sum在1和2中有几次,然后加到result里面就可以了,这里我们就得到了一个O(n^2) 的解法
Leetcode 383:赎金信
讲解前:
这道题和那个Anagram的题基本一样就是一种变形,这里我打算直接用数组来作为哈希表,然后呢我们可以把ransomnote中的出现的字母存进去,然后再magazine中去找,没找到一个就减去1,这里我Anagram不太一样的是,magazine中出现的字母有可能有多余的,所以数组中每个字母的值最后如果要返回True的话不能检查是否全部为0,但是可以检查是否都是小于等于0,因为多余的字母就会变成负数,但是不允许不够的情况出现使数组中出现正数
class Solution:
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
ransom = 26 * [0]
# add the count of letter of ransom to the list
for char in ransomNote:
ransom[ord(char) - ord('a')] += 1
# once we found a letter in magzine, decrease the count
for char in magazine:
ransom[ord(char) - ord('a')] -= 1
# if any positive num in the list, return false
for n in ransom:
if n > 0:
return False
return True
讲解后:
代码随想录里面的讲解和我的解法大同小异,他们是创建了两个数组然后把ransom和magazine里面的字母都统计一遍然后确定每个letter的count,ransom都是小于等于magazine的
Leetcode 15. 三数之和
讲解前:
这道题我之前是做过的,然后我记得用的方法就是双指针,我决定先尝试一下看能不能重新写出来之前的解法,因为之前也是看了题解才明白怎么做的我记得
我尝试先把数组排序然后两个指针left 和right 分别从两头开始遍历,然后直接把他们相加,找的相加的值和0的差值然后再left和right 之间找有没有那个差值,但是写起来不是很顺利
讲解后:
好吧我想起来了,这道题正确的解法,我们依然是要正常的从后往前遍历,只不过left 和 right指针是根据 i 移动的,每次只会检查从 i 到 end of list中有没有两个数相加刚好等于 i需要的变成0的值,这里卡哥重点讲解的去重操作我也有些印象,只不过在我上一次写的时候利用到的一个方法就是当然我们的起点 i 一定要进行去重,否则一定会再找到一次一样的元组,同时对于left 和 right,其实我们只需要对其中一个元素进行去重就可以了,例如我在接下来的代码中对left 进行了去重,因为我们要明白一个问题就是,当i 和 left 都进行了去重之后,下一次遍历开始找三元组的时候,我们已经确保了三元组中的两个数字都不一样,所以不用担心right 也出现同样的数字,我们正常的while loop检查三数之和的时候就可以解决right 的问题了
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
# create the result list and sort the list
# return none once we find the first element is positive
res = []
nums.sort()
if nums[0] > 0:
return res
# loop through the list with one number set
for i in range(len(nums)):
# make sure i does not repeat
if i > 0 and nums[i - 1] == nums[i]:
continue
# have the left and right pointer in respect of i
left = i + 1
right = len(nums) - 1
# check the current range have a three sum
while left < right:
three_sum = nums[i] + nums[left] + nums[right]
if three_sum < 0:
left = left + 1
elif three_sum > 0:
right = right - 1
# once we found, add to the res
else:
res.append([nums[i], nums[left], nums[right]])
# then move the left pointer make sure we are not check duplicate
left = left + 1
while nums[left] == nums[left - 1] and left < right:
left = left + 1
return res
Leetcode 18. 四数之和
讲解前:
看到这道题了之后我发现题目的描述基本和三数之和完全一样,都是需要做到去重然后找值,除了这里目标值是一个具体的target而不再是0,我心想我们应该还是可以用同样的技巧解题,只是这次遍历的时候我们需要两个两个一起遍历然后先求出 i 和 i + 1的值然后再去用left 和right 再找两个值
在尝试了一段时间之后终于找到了解法,大致来说就是就是抛弃上面的想法,我们遍历的过程是这样的
basically,我们add another loop,现在最外层的循环依然是 i 遍历整个数组,但是第二个数字我们用 j 来表示,然后 j 会从 i + 1 的位置也遍历整个数组,这时我们继续再用到 left 和 right 两个双指针,left 的值是 regard to j 的,然后呢剩下的和 three sum没太大区别,继续找 left 和 right 的值根据 target 和 i + j 的sum的差别,然后呢需要注意的时,我们对于 j 也要进行去重,就发生在left 和 right loop结束后,我们对 j 再increment的时候注意跳过重复的值
class Solution:
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
# create the result list and sort the list
res = []
nums.sort()
# iterate through the list
for i in range(len(nums) - 1):
if i > 0 and nums[i - 1] == nums[i]:
continue
j = i + 1
# another while loop to update j along the way
while j < len(nums) - 2:
two_sum = nums[i] + nums[j]
left = j + 1
right = len(nums) - 1
# search the current sublist to see if find
while left < right:
four_sum = two_sum + nums[left] + nums[right]
if four_sum < target:
left = left + 1
elif four_sum > target:
right = right - 1
else:
res.append([nums[i], nums[j], nums[left], nums[right]])
# move left make sure not repeating
left = left + 1
while left < right and nums[left - 1] == nums[left]:
left = left + 1
# make sure we skip the repeated nums[j] value
j = j + 1
while j < len(nums) - 2 and nums[j - 1] == nums[j]:
j = j + 1
return res
讲解后:
看完了卡哥的讲解之后我发现了我的代码思路和他的差别在于我直接在这个四数之和里面放弃了剪枝的操作,因为我确实一开始还想用三数之和的思路写了一个提前的if statement来检查排序之后数组的第一个数和target作比较提前返回值,但是我没考虑负数的情况,所以出现bug之后我直接抛弃了剪枝的操作,但是卡哥这里保留了剪枝的操作只是更改了确定他们都大于0,以下是我的代码然后更新增加了剪枝操作的版本
class Solution:
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
# create the result list and sort the list
res = []
nums.sort()
# iterate through the list
for i in range(len(nums) - 1):
# once we know it is impossible to have a result, return empty list
if nums[i] > target and target > 0:
break
if i > 0 and nums[i - 1] == nums[i]:
continue
j = i + 1
# another while loop to update j along the way
while j < len(nums) - 2:
two_sum = nums[i] + nums[j]
# break the loop if we already know that the smallest two_sum is
# already bigger than the target when target is a positive number
if target > 0 and two_sum > target:
break
left = j + 1
right = len(nums) - 1
# search the current sublist to see if find
while left < right:
four_sum = two_sum + nums[left] + nums[right]
if four_sum < target:
left = left + 1
elif four_sum > target:
right = right - 1
else:
res.append([nums[i], nums[j], nums[left], nums[right]])
# move left make sure not repeating
left = left + 1
while left < right and nums[left - 1] == nums[left]:
left = left + 1
# make sure we skip the repeated nums[j] value
j = j + 1
while j < len(nums) - 2 and nums[j - 1] == nums[j]:
j = j + 1
return res