目录
454. 四数相加II
题目链接:454. 四数相加 II - 力扣(LeetCode)
文章讲解:代码随想录
解题卡点:只想到大概思路,具体没想明白
在集合中判断一个元素是否出现过→哈希法。可以先遍历数组A和数组B,储存所有可能性在哈希表中,然后一一遍历数组C和数组D的组合,看0-(C+D)元素是否在哈希表中出现,如果出现,就找到了合适的元组。
本题是1. 两数之和的升级版,由于是四个独立的数组,所以不需要考虑去重的操作。
Tips:
在CD数组的查找过程中,若使用索引dict[ ]去查找,普通字典会报错,默认字典会创建该元素的键值对影响字典,因此使用字典的get( )方法进行查找。
字典获取值,dict.get(key, default=""),第一个参数为查找的键,第二个参数为键不存在时的返回内容。如果default没有设置,会返回None。
class Solution:
def fourSumCount(self, nums1: List[int], nums2: List[int], nums3: List[int], nums4: List[int]) -> int:
rec = collections.defaultdict(int)
count = 0
for i in nums1:
for j in nums2:
rec[i+j] += 1
for i in nums3:
for j in nums4:
count += rec.get(0-i-j, 0)
# count += rec[0-i-j] 错误写法,会导致字典中创建0-i-j的键值对
return count
# 时间复杂度: O(n^2)
# 空间复杂度: O(n^2)
# 最坏情况下前两个nums和后两个nums的值各不相同,相加产生的数字个数为n^2
383. 赎金信
文章讲解:代码随想录
解题卡点:无
242. 有效字母异位词,相当于求字符串a和字符串b能否相互组成。383. 赎金信,相当于求字符串a能否由字符串b组成。
使用一个长度为26的数组来记录magazine里字母出现的次数,再用ransomNote去验证这个数组是否包含了ransomNote所需要的所有字母。
本题为数组在哈希法中的应用。另外在本题的情况下,使用map的空间消耗要比数组大一些的,因为map要维护红黑树或者哈希表,而且还要做哈希函数。数组更加简单直接有效。
class Solution:
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
rec = [0]*26
for i in magazine:
rec[ord(i)-ord('a')] += 1
for i in ransomNote:
rec[ord(i)-ord('a')] -= 1
if rec[ord(i)-ord('a')] < 0:
return False
return True
# 时间复杂度 O(n)
# 空间复杂度 O(1)
15. 三数之和
文章讲解:代码随想录
解题卡点:bc的去重没有想出来
哈希解法:两个for循环确定两个数值,第三个数值用0-a-b代替,思路没有问题,但题目要求元素不重复,维护去重条件较为麻烦。
双指针法:首先对列表进行排序,然后使用for循环确定a,且将a的下一个设为b,将列表的末尾设为c。sum=a+b+c。b向右移动sum不变或变大,c向左移动sum不变或变小。内层使用while循环,若sum小于0,b向右移动使sum变大,若sum大于0,c向左移动使sum变小,直至bc相遇。在bc相遇的过程中可能会有sum为0,记录此时的abc。
一些小要点:
剪枝:排序后的第一个元素a就是列表中最小的元素,若a大于0,则整体的sum一定大于0。因此对a进行判断,若大于0直接返回空列表。
sum=0后如何移动bc:当sum=0时,右移b可能变大超过0,左移c可能变小小于0,只有同时让b右移和让c左移,才可能继续出现为0的情况。
a的去重:a是nums里for循环遍历的元素,重复了直接跳过去,但必须是判断nums[i]与nums[i-1]是否相同,不能判断nums[i]与nums[i+1]是否相同。题目需要不重复的三元组,但元组内的元素可以重复。若使用nums[i]与nums[i+1],当a为第一个元素时,对于[-1,-1,2]这样的列表,会把第二个需要的-1也给跳过了。
bc的去重:对于[0,-1,-1,-1,-1,2,2,2,2]这样的列表,在开始时a为0,b为第一个-1,c为最后一个2。随后进行while循环,b右移,c左移,依然能使sum=0,但元素重复。因此当sum=0之后,还需要判断b和b的右边是不是相同,c和c的左边是不是相同,若相同则继续移动相应指针。
1. 两数之和可以使用双指针吗:不能使用双指针,因为1. 两数之和要求返回的是索引下标, 而双指针法一定要排序,一旦排序之后原数组的索引就被改变了。如果要求返回的是数值,则可以使用双指针法。
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
res = []
nums.sort() # 对列表排序,有序列表才能使用双指针
for idx, a in enumerate(nums):
if a > 0: # 提前剪枝
return res
if idx > 0 and a == nums[idx-1]: # 防止a重复
continue
idxb = idx+1
idxc = len(nums)-1
while idxb < idxc: # 直至bc相遇才退出循环
b = nums[idxb]
c = nums[idxc]
if a+b+c > 0: # sum大则左移c使整体变小
idxc -= 1
elif a+b+c < 0: # sum小则右移b使整体变大
idxb += 1
else:
res.append([a, b, c])
while idxb < idxc and b == nums[idxb+1]: # 防止b重复
idxb += 1
while idxb < idxc and c == nums[idxc-1]: # 防止c重复
idxc -= 1
idxb += 1
idxc -= 1
return res
# 时间复杂度 O(n^2)
# 空间复杂度 O(1)
18. 四数之和
文章讲解:代码随想录
解题卡点:无
同15. 三数之和。两数之和双指针,三数之和外层套一个for循环,四数之和外层套两个for循环。
剪枝时要注意,第一个元素大于0,则排序后整体sum单调递增,在第一个元素大于target且单调递增的情况下,后面元素的加入一定会使sum超过target,此时停止循环返回空列表。同理,在第二层剪枝中,先判断第一个元素是否大于0,是否单调递增,若前两个元素大于target,停止循环返回空列表。
class Solution:
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
res = []
nums.sort()
for idxa in range(len(nums)):
if nums[idxa] > target and nums[idxa] > 0: # 第一层剪枝
break
if idxa > 0 and nums[idxa] == nums[idxa-1]: # 防止a重复
continue
for idxb in range(idxa+1, len(nums)):
if nums[idxa] + nums[idxb] > target and nums[idxa] > 0: # 第二层剪枝
break
if idxb > idxa+1 and nums[idxb] == nums[idxb-1]: # 防止b重复
continue
idxc = idxb+1
idxd = len(nums)-1
while idxc < idxd:
a = nums[idxa]
b = nums[idxb]
c = nums[idxc]
d = nums[idxd]
sum = a + b + c +d
if sum < target:
idxc += 1
elif sum > target:
idxd -= 1
else:
res.append([a, b, c, d])
while idxc < idxd and c == nums[idxc+1]: # 防止c重复
idxc += 1
while idxc < idxd and d == nums[idxd-1]: # 防止d重复
idxd -= 1
idxc += 1
idxd -= 1
return res
# 时间复杂度 O(n^3)
# 空间复杂度 O(1)