哈希表系列2
- 1 两数之和
- 本题思路
- 代码随想录的代码
- 力扣的示例代码
- 454 四数相加II
- 本题思路
- 代码随想录的代码
- 力扣的示例代码
- 15 三数之和
- 本题思路
- 代码随想录的代码
- 力扣的示例代码
- 18 四数之和
- 代码随想录的代码
- 力扣的示例代码
1 两数之和
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
示例1:
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
示例2:
输入:nums = [3,2,4], target = 6
输出:[1,2]
示例3:
输入:nums = [3,3], target = 6
输出:[0,1]
本题思路
除了暴力法,第一时间没有想出其他思路,去学习代码随想录的思路。
242 有效的字母异位词 (opens new window)这道题目是用数组作为哈希表来解决哈希问题,349. 两个数组的交集 (opens new window)这道题目是通过set作为哈希表来解决哈希问题。
首先我在强调一下 什么时候使用哈希法,当我们需要查询一个元素是否出现过,或者一个元素是否在集合里的时候,就要第一时间想到哈希法。
本题需要一个集合来存放我们遍历过的元素,然后在遍历数组的时候去询问这个集合,某元素是否遍历过,也就是 是否出现在这个集合。
那么我们就应该想到使用哈希法了。
因为本地,我们不仅要知道元素有没有遍历过,还要知道这个元素对应的下标,需要使用 key value结构来存放,key来存元素,value来存下标,那么使用map正合适。
再来看一下使用数组和set来做哈希法的局限。
数组的大小是受限制的,而且如果元素很少,而哈希值太大会造成内存空间的浪费。
set是一个集合,里面放的元素只能是一个key,而两数之和这道题目,不仅要判断y是否存在而且还要记录y的下标位置,因为要返回x 和 y的下标。所以set 也不能用。
此时就要选择另一种数据结构:map ,map是一种key value的存储结构,可以用key保存数值,用value在保存数值所在的下标。
map目的用来存放我们访问过的元素,因为遍历数组的时候,需要记录我们之前遍历过哪些元素和对应的下标,这样才能找到与当前元素相匹配的(也就是相加等于target)
接下来是map中key和value分别表示什么。
这道题 我们需要 给出一个元素,判断这个元素是否出现过,如果出现过,返回这个元素的下标。
那么判断元素是否出现,这个元素就要作为key,所以数组中的元素作为key,有key对应的就是value,value用来存下标。
所以 map中的存储结构为 {key:数据元素,value:数组元素对应的下标}。
在遍历数组的时候,只需要向map去查询是否有和目前遍历元素匹配的数值,如果有,就找到的匹配对,如果没有,就把目前遍历的元素放进map中,因为map存放的就是我们访问过的元素。
本题其实有四个重点:为什么会想到用哈希表;哈希表为什么用map;本题map是用来存什么的;map中的key和value用来存什么的。
代码随想录的代码
(版本一) 使用字典
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
records = dict()
for index, value in enumerate(nums):
if target - value in records: # 遍历当前元素,并在map中寻找是否有匹配的key
return [records[target- value], index]
records[value] = index # 遍历当前元素,并在map中寻找是否有匹配的key
return []
(版本二)使用集合
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
#创建一个集合来存储我们目前看到的数字
seen = set()
for i, num in enumerate(nums):
complement = target - num
if complement in seen:
return [nums.index(complement), i]
seen.add(num)
(版本三)使用双指针
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
# 对输入列表进行排序
nums_sorted = sorted(nums)
# 使用双指针
left = 0
right = len(nums_sorted) - 1
while left < right:
current_sum = nums_sorted[left] + nums_sorted[right]
if current_sum == target:
# 如果和等于目标数,则返回两个数的下标
left_index = nums.index(nums_sorted[left])
right_index = nums.index(nums_sorted[right])
if left_index == right_index:
right_index = nums[left_index+1:].index(nums_sorted[right]) + left_index + 1
return [left_index, right_index]
elif current_sum < target:
# 如果总和小于目标,则将左侧指针向右移动
left += 1
else:
# 如果总和大于目标值,则将右指针向左移动
right -= 1
(版本四)暴力法
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
for i in range(len(nums)):
for j in range(i+1, len(nums)):
if nums[i] + nums[j] == target:
return [i,j]
力扣的示例代码
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
n = len(nums)
harsh_table = dict()
for i in range(n):
if target - nums[i] in harsh_table:
return [harsh_table[target - nums[i]],i]
else:
harsh_table[nums[i]] = i
return []
454 四数相加II
给你四个整数数组 nums1、nums2、nums3 和 nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:
0 <= i, j, k, l < n
nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0
示例1:
输入:nums1 = [1,2], nums2 = [-2,-1], nums3 = [-1,2], nums4 = [0,2]
输出:2
解释:两个元组如下:
1:(0, 0, 0, 1) -> nums1[0] + nums2[0] + nums3[0] + nums4[1] = 1 + (-2) + (-1) + 2 = 0
2:(1, 1, 0, 0) -> nums1[1] + nums2[1] + nums3[0] + nums4[0] = 2 + (-1) + (-1) + 0 = 0
示例2:
输入:nums1 = [0], nums2 = [0], nums3 = [0], nums4 = [0]
输出:1
本题思路
此题第一次思考,只能想到拿出一个值,然后三重循环遍历。
还是太年轻了,为什么不从中间砍开呢?二分法的思想不就在此体现了吗?从中间拆分的话,就只需要两重循环遍历了。
以下是代码随想录给出的思路。
代码随想录的代码
(版本一) 使用字典
class Solution(object):
def fourSumCount(self, nums1, nums2, nums3, nums4):
# 使用字典存储nums1和nums2中的元素及其和
hashmap = dict()
for n1 in nums1:
for n2 in nums2:
if n1 + n2 in hashmap:
hashmap[n1+n2] += 1
else:
hashmap[n1+n2] = 1
# 如果 -(n1+n2) 存在于nums3和nums4, 存入结果
count = 0
for n3 in nums3:
for n4 in nums4:
key = - n3 - n4
if key in hashmap:
count += hashmap[key]
return count
(版本二) 使用字典
class Solution(object):
def fourSumCount(self, nums1, nums2, nums3, nums4):
# 使用字典存储nums1和nums2中的元素及其和
hashmap = dict()
for n1 in nums1:
for n2 in nums2:
hashmap[n1+n2] = hashmap.get(n1+n2, 0) + 1
# 如果 -(n1+n2) 存在于nums3和nums4, 存入结果
count = 0
for n3 in nums3:
for n4 in nums4:
key = - n3 - n4
if key in hashmap:
count += hashmap[key]
return count
(版本三)使用 defaultdict
from collections import defaultdict
class Solution:
def fourSumCount(self, nums1: list, nums2: list, nums3: list, nums4: list) -> int:
rec, cnt = defaultdict(lambda : 0), 0
for i in nums1:
for j in nums2:
rec[i+j] += 1
for i in nums3:
for j in nums4:
cnt += rec.get(-(i+j), 0)
return cnt
力扣的示例代码
class Solution:
def fourSumCount(self, nums1: List[int], nums2: List[int], nums3: List[int], nums4: List[int]) -> int:
ds1 = collections.Counter(nums1)
ds2 = collections.Counter(nums2)
ds3 = collections.Counter(nums3)
ds4 = collections.Counter(nums4)
# nums1 and nums2
add12 = collections.defaultdict(int)
add34 = collections.defaultdict(int)
for k1, v1 in ds1.items():
for k2, v2 in ds2.items():
add12[k1 + k2] += (v1 * v2)
for k1, v1 in ds3.items():
for k2, v2 in ds4.items():
add34[k1 + k2] += (v1 * v2)
count = 0
for k1, v1 in add12.items():
if -k1 in add34:
count += (v1 * add34[-k1])
return count
15 三数之和
给你一个整数数组 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] 。
注意,输出的顺序和三元组的顺序并不重要。
示例2:
输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0 。
本题思路
不多说了,不可能会的,想不到使用双指针,只能想到先对数组排个序。
双指针思路在本题中是容易想到的,难的是,如何正确理清“去重逻辑”???
这是一个之前没有接触过,并且很难的点,解答不粘贴在这里,放一个代码随想录的链接,N刷的时候要努力弄清去重逻辑。
代码随想录:三数之和
代码随想录的代码
(版本一) 双指针
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
result = []
nums.sort()
for i in range(len(nums)):
# 如果第一个元素已经大于0,不需要进一步检查
if nums[i] > 0:
return result
# 跳过相同的元素以避免重复
if i > 0 and nums[i] == nums[i - 1]:
continue
left = i + 1
right = len(nums) - 1
while right > left:
sum_ = nums[i] + nums[left] + nums[right]
if sum_ < 0:
left += 1
elif sum_ > 0:
right -= 1
else:
result.append([nums[i], nums[left], nums[right]])
# 跳过相同的元素以避免重复
while right > left and nums[right] == nums[right - 1]:
right -= 1
while right > left and nums[left] == nums[left + 1]:
left += 1
right -= 1
left += 1
return result
(版本二) 使用字典(看不懂)
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
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
力扣的示例代码
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
ans = []
counts = {}
for i in nums:
counts[i] = counts.get(i, 0) + 1
nums = sorted(counts)
for i, num in enumerate(nums):
if counts[num] > 1:
if num == 0:
if counts[num] > 2:
ans.append([0, 0, 0])
continue
else:
if -num * 2 in counts:
ans.append([num, num, -2 * num])
if num < 0:
two_sum = -num
left = bisect.bisect_left(nums, (two_sum - nums[-1]), i + 1)
for j in nums[left: bisect.bisect_right(nums, (two_sum // 2), left)]:
k = two_sum - j
if k in counts and k != j:
ans.append([num, j, k])
return ans
18 四数之和
题目不列在这里了,好难。
直接放代码随想录的链接。
代码随想录–四数之和
代码随想录的代码
(版本一) 双指针
class Solution:
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
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
(版本二) 使用字典
class Solution(object):
def fourSum(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: List[List[int]]
"""
# 创建一个字典来存储输入列表中每个数字的频率
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]
力扣的示例代码
class Solution:
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
ans = []
nums.sort()
len_nums = len(nums)
table = {}
for j in range(len_nums-1, 2, -1):
if j < len_nums-1 and nums[j] == nums[j+1]:
continue
if 4 * nums[j] < target:
break
if nums[j] + 3*nums[0] > target:
continue
for i in range(j-1, 1, -1):
if i < j-1 and nums[i] == nums[i+1]:
continue
if nums[j] + 3*nums[i] < target:
break
if nums[j] + nums[i] + 2*nums[0] > target:
continue
table.setdefault(nums[i] + nums[j], []).append((i, j))
for i in range(len_nums-3):
if i > 0 and nums[i] == nums[i-1]:
continue
if nums[i] * 4 > target:
break
if nums[i] + 3 * nums[-1] < target:
continue
for j in range(i + 1, len_nums-2):
if j > i + 1 and nums[j] == nums[j-1]:
continue
if nums[i] + 3*nums[j] > target:
break
if nums[i] + nums[j] + 2*nums[-1] < target:
continue
for index, jndex in table.get(target - nums[i] - nums[j], []):
if j < index:
ans.append([nums[i], nums[j], nums[index], nums[jndex]])
return ans