【代码随想录】哈希表2
wbfwonderful Lv4

383. 赎金信

link

给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。

如果可以,返回 true ;否则返回 false 。

magazine 中的每个字符只能在 ransomNote 中使用一次。

思路

统计 magazine 和 ransomNote 中的字符数量,然后再判断大小(直接比较 Counter)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution:
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
cm = Counter()
cr = Counter()

for i in magazine:
cm[i] += 1
for i in ransomNote:
cr[i] += 1

if cm >= cr:
return True
else:
return False

15. 三数之和

link

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。

注意:答案中不可以包含重复的三元组。

示例 1:

输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。

思路

本题无法使用三重循环。因为如果所有的数都为 0,则需要需要大量去重。

「不重复」的本质是什么?我们保持三重循环的大框架不变,只需要保证:

  • 第二重循环枚举到的元素不小于当前第一重循环枚举到的元素;
  • 第三重循环枚举到的元素不小于当前第二重循环枚举到的元素。

也就是说,我们枚举的三元组 (a,b,c) 满足 a≤b≤c,保证了只有 (a,b,c) 这个顺序会被枚举到,而 (b,a,c)、(c,b,a) 等等这些不会,这样就减少了重复。同时,对于每一重循环而言,相邻两次枚举的元素不能相同,否则也会造成重复。

但是上述方法仍为三重循环。如果我们固定了前两重循环枚举到的元素 a 和 b,那么只有唯一的 c 满足 a+b+c=0。当第二重循环往后枚举一个元素 b’ 时,由于 b’>b,那么满足 a+b’+c’=0 的 c’ 一定有 c’<c,即在数组中一定出现在 c 的左侧。也就是说,我们可以从小到大枚举 b,同时从大到小枚举 c,即第二重循环和第三重循环实际上是并列的关系。故枚举 b 为左指针,枚举 c 为右指针。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from typing import List

class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
nums.sort()
res = []

for i in range(len(nums)):
if i > 0 and nums[i] == nums[i - 1]: # 跳过重复a
continue

left, right = i + 1, len(nums) - 1

while left < right:
total = nums[i] + nums[left] + nums[right]
if total == 0:
res.append([nums[i], nums[left], nums[right]])
left += 1
right -= 1
# 跳过重复b
while left < right and nums[left] == nums[left - 1]:
left += 1
# 跳过重复c
while left < right and nums[right] == nums[right + 1]:
right -= 1
elif total < 0:
left += 1
else:
right -= 1

return res

思考

两数之和 能不能用双指针法呢?

两数之和不能使用双指针法,因为1.两数之和 (opens new window)要求返回的是索引下标, 而双指针法一定要排序,一旦排序之后原数组的索引就被改变了。

如果1.两数之和 (opens new window)要求返回的是数值的话,就可以使用双指针法了

18. 四数之和

link

给你一个由 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
你可以按 任意顺序 返回答案 。

思路

类似于三数之和,同样是使用双指针法减少一层循环。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Solution:
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
nums.sort()

res = []

for i in range(len(nums)):
if i > 0 and nums[i] == nums[i-1]:
continue
for j in range(i + 1, len(nums)):
if j > i + 1 and nums[j] == nums[j - 1]:
continue

left, right = j + 1, len(nums) - 1

while left < right:
total = nums[i] + nums[j] + nums[left] + nums[right]

if total == target:
res.append([nums[i], nums[j], nums[left], nums[right]])
left += 1
right -= 1

while left < right and nums[left] == nums[left - 1]:
left += 1

while left < right and nums[right] == nums[right + 1]:
right -= 1

elif total < target:
left += 1
else:
right -= 1
return res