代码随想录算法训练营第七天 | 哈希表part02
454 四数相加II
可以想到用字典的方法来做。把前两个数组各位相加的和存到一个字典里面。
然后遍历后两个数组,看看相加后的相反数在不在字典里面,如果在字典里面,就加上键对应的值。
res = dict()
for i in nums1:
for j in nums2:
res[i+j] = res.get(i+j,0) + 1
count = 0
for i in nums3:
for j in nums4:
key = i + j
if -key in res:
count += res[-key]
return count
383 赎金信
方法1:字典
同样,使用字典,首先把后者中所有的字符存到字典里。
同时,也可以把前者所有的字符都存到字典里,可以对对应键的值进行比较。
如果前者的值小于等于后者的,那就return true
counts = {}
for c in magazine:
counts[c] = counts.get(c, 0) + 1
for c in ransomNote:
if c not in counts or counts[c] == 0:
return False
counts[c] -= 1
return True
方法2:数组
ransom_count = [0] * 26
magazine_count = [0] * 26
for c in ransomNote:
ransom_count[ord(c) - ord('a')] += 1
for c in magazine:
magazine_count[ord(c) - ord('a')] += 1
return all(ransom_count[i] <= magazine_count[i] for i in range(26))
方法3:使用count
for char in ransomNote:
if char in magazine and ransomNote.count(char) <= magazine.count(char):
continue
else:
return False
return True
15 三数之和
思路:很巧妙的一道题目,如果三层for循环是可以做的,但是去重很难。
所以考虑快慢指针法。使用三个指针,i left right
然后计算每次的三个数之和,如果>0的话,那么就right--,如果<0,那么就left++
每轮完了之后让i++
可是如何保证去重呢?
因为每轮完了之后有i++,所以nums[i] == nums[i-1] continue
因为left和right也有可能重复。
那么就
while right > left and nums[left] == nums[left + 1] :
left += 1
right方向也是同理。
跳出while循环之后,让left += 1 ,right -= 1。
完整代码:
result = []
nums.sort()
for i in range(len(nums)):
if nums[i] >= 0 :
return result
if nums[i] == nums[i-1]:
continue
left = i + 1
right = len(nums) - 1
while left < right:
sum_ = nums[i] + nums[left] + nums[right]
if sum_ > 0:
right -= 1
elif sum_ < 0:
left += 1
else :
result.append([nums[i],nums[left],nums[right]])
while right > left and nums[left] == nums[left + 1] :
left += 1
while right > left and nums[right] == nums[right - 1] :
right -= 1
left += 1
right -= 1
return result
上面代码中有一个致命的问题,那就是如果是[0,0,0]是过不去的。
改变判断条件如下即可:
for i in range(len(nums)):
if nums[i] > 0 :
return result
# i > 0 是为了确保我们不会在第一次循环时就跳过数组的第一个元素。
if i > 0 and nums[i] == nums[i-1]:
continue
第二种方法:使用字典(时间复杂度高)
result = []
nums.sort()
# 找出a + b + c = 0
# a = nums[i], b = nums[j], c = -(a + b)
for i in range(len(nums)):
# 排序之后如果第一个元素已经大于零,那么不可能凑成三元组
if nums[i] > 0:
break
if i > 0 and nums[i] == nums[i - 1]: #三元组元素a去重
continue
d = {}
for j in range(i + 1, len(nums)):
if j > i + 2 and nums[j] == nums[j-1] == nums[j-2]: # 三元组元素b去重
continue
c = 0 - (nums[i] + nums[j])
if c in d:
result.append([nums[i], nums[j], c])
d.pop(c) # 三元组元素c去重
else:
d[nums[j]] = j
return result
18 四数之和
初见时感觉和三数之和差不多,不过就是又多加了一个数。但是如果我们用快慢指针的话显然是不行了。
ps:还是得用快慢指针,只不过多加一个遍历就可以了。
一定记住,剪枝,去重
方法1:双指针
对于 if j > i+1 and nums[j] == nums[j-1]:
对于 j = i+1(即第一次进入第二层循环时),我们不需要检查 nums[j] 是否与 nums[j-1] 相同,因为 j 是在 i+1 位置,第一个可能不同的元素。所以我们从 j > i+1 开始去重,确保我们不会在刚进入循环时就跳过所有元素。
nums.sort()
n = len(nums)
result = []
for i in range(n):
if nums[i] > target and nums[i] > 0 and target > 0:# 剪枝(可省)
break
if i > 0 and nums[i] == nums[i-1]:# 去重
continue
for j in range(i+1, n):
if nums[i] + nums[j] > target and target > 0: #剪枝(可省)
break
if j > i+1 and nums[j] == nums[j-1]: # 去重
continue
left, right = j+1, n-1
while left < right:
s = nums[i] + nums[j] + nums[left] + nums[right]
if s == target:
result.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
elif s < target:
left += 1
else:
right -= 1
return result
方法2:字典
freq = {}
for num in nums:
freq[num] = freq.get(num, 0) + 1
# 创建一个集合来存储最终答案,并遍历4个数字的所有唯一组合
ans = set()
for i in range(len(nums)):
for j in range(i + 1, len(nums)):
for k in range(j + 1, len(nums)):
val = target - (nums[i] + nums[j] + nums[k])
if val in freq:
# 确保没有重复
count = (nums[i] == val) + (nums[j] == val) + (nums[k] == val)
if freq[val] > count:
ans.add(tuple(sorted([nums[i], nums[j], nums[k], val])))
return [list(x) for x in ans]