导语
leetcode刷题笔记记录,本篇博客记录哈希表部分的题目,主要题目包括:
- 454 四数相加II
- 383 赎金信
- 15 三数之和
- 18 四数之和
Leetcode 454 四数相加II
题目描述
给你四个整数数组 nums1、nums2、nums3 和 nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:
0 <= i, j, k, l < nnums1[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
提示:
n == nums1.lengthn == nums2.lengthn == nums3.lengthn == nums4.length1 <= n <= 200-2^28 <= nums1[i], nums2[i], nums3[i], nums4[i] <= 2^28
解法
本题目操作4个数组,因此不存在重复现象,简单的暴力法复杂度高达,这里可以借助与两数之和相同的思路,将前两个数组之和提前计算出存入一个字典map中,到时候进行双层循环计算后两个数组中元素的和,然后查找差值是否位于已经计算好的字典即可。这样可以将复杂度降低至。
完整代码如下:
class Solution:
def fourSumCount(self, nums1: List[int], nums2: List[int], nums3: List[int], nums4: List[int]) -> int:
sum2cnt = defaultdict(int)
count = 0
for a in nums1:
for b in nums2:
sum2cnt[a+b] += 1
for c in nums3:
for d in nums4:
count += sum2cnt[-c-d]
return count
Leetcode 383 赎金信
题目描述
给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。如果可以,返回 true ;否则返回 false 。magazine 中的每个字符只能在 ransomNote 中使用一次。
示例 1:
输入: ransomNote = "a", magazine = "b"
输出: false
提示:
1 <= ransomNote.length, magazine.length <= 105ransomNote和magazine由小写英文字母组成
解法
这道题目和之前的异位词题目很像,都是应用数组的很好场景,因为只处理26个小写字母,所以很容易通过数组解决,解法如下:
class Solution:
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
ransom_char, magazine_char = [0] * 26, [0] * 26
for char in ransomNote:
ransom_char[ord(char)-ord('a')] += 1
for char in magazine:
magazine_char[ord(char)-ord('a')] += 1
for i in range(26):
if ransom_char[i] > magazine_char[i]:
return False
return True
Leetcode 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] 。
注意,输出的顺序和三元组的顺序并不重要。
提示:
3 <= nums.length <= 3000-105 <= nums[i] <= 105
解法
本题目相对于之前的题目就复杂很多了,由于可能存在重复,所以并不适用于哈希表,这里使用双指针的方式来解决。
首先,需要将数组排序(一般双指针题目都需要这样),然后,最外层for循环从前往后遍历节点i,同时定义left和right的相向双指针如图所示。接下来如何移动left 和right呢, 如果nums[i] + nums[left] + nums[right] > 0 就说明 此时三数之和大了,因为数组是排序后了,所以right下标就应该向左移动,这样才能让三数之和小一些。如果 nums[i] + nums[left] + nums[right] < 0 说明 此时 三数之和小了,left 就向右移动,才能让三数之和大一些,直到left与right相遇为止。
这道题目的核心在于去重,
a的去重
a 如果重复了应该直接跳过去。但这里有一个问题,是判断 nums[i] 与 nums[i + 1]是否相同,还是判断 nums[i] 与 nums[i-1] 是否相同。
二者都是和 nums[i]进行比较,差异在于是比较它的前一个,还是比较他的后一个。这里分别进行考虑:
如果考虑后一个:
if (nums[i] == nums[i + 1]) { // 去重操作
continue;
}
那就把三元组中出现重复元素的情况直接pass掉了。 例如{-1, -1 ,2} 这组数据,当遍历到第一个-1 的时候,判断 下一个也是-1,那这组数据就pass了。但是,我们要做的是 不能有重复的三元组,但三元组内的元素是可以重复的!
所以这里是有两个重复的维度。如果考虑前一个,
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
这么写就是当前使用 nums[i],我们判断前一位是不是一样的元素,再看 {-1, -1 ,2} 这组数据,当遍历到 第一个 -1 的时候,只要前一位没有-1,那么 {-1, -1 ,2} 这组数据一样可以收录到结果集里。这个才是需要的方法。
完整代码如下:
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
nums.sort()
result_l = []
for i in range(len(nums)):
if nums[i] > 0:
return result_l
if i>0 and nums[i]==nums[i-1]:
continue
left = i + 1
right = len(nums) - 1
while left < right:
if nums[i] + nums[left] + nums[right] == 0:
result_l.append([nums[i], 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 nums[i] + nums[left] + nums[right] > 0:
right -= 1
else:
left += 1
return result_l
Leetcode 18 四数之和
这道题目是更加复杂的求和,但整体思想与三数之和类似。首先要做的是对数组进行排序,接着定义最外层指针k遍历数组。
由于这里的target并不一定是正数,所以一开始判断剪枝(提前去掉不可能的情况)时,需要判断target是否为正数。接着,需要先对最外层循环k去重,思路与三数之和的最外层循环类似。
然后,第二层循环就是三数之和的最外层循环,这里需要注意的是i是从k+1开始的,之后套入三数之和的代码即可,完整代码如下:
class Solution:
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
result_l = []
n = len(nums)
nums.sort()
for k in range(n):
# 剪枝
if target>0 and nums[k]>target:
return result_l
# 去重
if k>0 and nums[k]==nums[k-1]:
continue
for i in range(k+1, n):
if target>0 and nums[k]+nums[i]>target:
break
# 去重
if i>k+1 and nums[i]==nums[i-1]:
continue
left = i + 1
right = len(nums) - 1
while left < right:
if nums[k] + nums[i] + nums[left] + nums[right] == target:
result_l.append([nums[k], nums[i], 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 nums[k] + nums[i] + nums[left] + nums[right] > target:
right -= 1
else:
left += 1
return result_l
这里需要注意的是,第二层循环的剪枝break不能换成return,因为第一层循环中可能还存在符合情况,所以第二层剪枝应该使用break,而不是return。当然,剪枝的操作不是必须的,也仅能提升一点速度。