今日任务:
1)454.四数相加II
2)383. 赎金信
3)15. 三数之和
4)18. 四数之和
454.四数相加II
题目链接:454. 四数相加 II - 力扣(LeetCode)
题目:
给你四个整数数组 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
文章讲解:代码随想录 (programmercarl.com)
视频讲解:学透哈希表,map使用有技巧!LeetCode:454.四数相加II哔哩哔哩bilibili
思路:
这题最直观的方法是暴力法,用2个for循环嵌套,时间复杂度O(n^4)。
我们可以借助哈希表将时间复杂度降为O(n^2)
具体思路是利用2个for循环嵌套,先计算出AB数组的和,将所有可能出现的和的情况用哈希表保存,然后再用两个for循环嵌套遍历CD数组,在AB中哈希表中去找符合条件的差值。
这里虽然也有4个for循环,但这里不是4个for循环嵌套,这里两个并列的2个for循环嵌套,所以时间复杂度变为了O(2n^2) --> O(n^2)
(哈希表应该选map,因为返回的是下标,也就不仅需要记录值,还需要记录下标,用set不合适,同时累加和不是可控范围的值,用数组不合适)
class Solution:
def fourSumCount(self, nums1: list[int], nums2: list[int], nums3: list[int], nums4: list[int]) -> int:
dic = dict()
# todo 1.遍历AB数组得到元素累加和
for a in nums1:
for b in nums2:
dic[a+b] = dic.get(a+b, 0) + 1
# todo 2.遍历CD数组得到元素与0差值,在哈希表中寻找
cnt = 0
for c in nums3:
for d in nums4:
div = -c-d
if div in dic:
cnt += dic[div]
return cnt
感想:
这题还算比较简单,4个数组求和,分为两大组看即可,而且返回下标,不用考虑重复(key是不会重复的,如果返回值,就考虑是否去重了),很好的体现了用哈希表提高效率。
383. 赎金信
题目:
给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。
如果可以,返回 true ;否则返回 false 。
magazine 中的每个字符只能在 ransomNote 中使用一次。示例 1:
输入:
ransomNote = "a"
magazine = "b"
输出:false示例 2:
输入:
ransomNote = "aa"
magazine = "ab"
输出:false示例 3:
输入:
ransomNote = "aa"
magazine = "aab"
输出:true
文章讲解:代码随想录 (programmercarl.com)
思路:
这题与前面242.有效的字母异位词是一类型的题目,把其中一个数组用哈希表存储,这题直接把magazine转为哈希表即可
哈希表可以选择集合,也可以选择数组解决(只有26个字母)
class Solution:
# 使用字典
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
res = dict()
# todo 1.magazine转哈希表
for i in magazine:
res[i] = res.get(i, 0) + 1
# todo 2.在哈希表消去ransomNote
for i in ransomNote:
res[i] = res.get(i, 0) - 1
if res[i] < 0:
return False
return True
# 使用数组
def canConstruct2(self, ransomNote: str, magazine: str) -> bool:
res = [0]*26
# todo 1.magazine转哈希表
for i in magazine:
res[ord(i) - ord('a')] += 1
# todo 2.在哈希表消去ransomNote
for i in ransomNote:
res[ord(i) - ord('a')] -= 1
if res[ord(i) - ord('a')] < 0:
return False
return True
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,1,1]
输出:[]
解释:唯一可能的三元组和不为 0 。示例 3:
输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0
文章讲解:代码随想录 (programmercarl.com)
视频讲解:梦破碎的地方!| LeetCode:15.三数之和哔哩哔哩bilibili
思路:
这题采用双指针法,思路不难,但有几个核心点
首先说思路
1)先对数组进行排序
2)遍历数组,确定第一个数a
3)因为同一个元素不能重复(注意不是相同的值),所以在第一个数后面去找符合要求的第二个数b、第三个数c,如下图
4)将i,left,right三个指针的值相加,如果比target大,则右指针左移,比target大,则左指针右移,相等则保留值
核心点
1)因为是返回值,所以这里我们可以对数组进行排序
2)也是正因为要返回值,并且不能返回相同的数组,所以要注意a的去重,bc的去重,也就是两个指针的去重
class Solution:
# 采用双指针法
def threeSum(self, nums: list[int]) -> list[list[int]]:
res = []
# todo 1.先对数组进行排序
nums.sort()
# todo 2.遍历数组定义指针
for i in range(len(nums)):
# todo 2.1 a的去重:a就是nums[i],如果以a开头已经找过一遍了,那么再以a开头去找,会产生重复数组
if i > 0 and nums[i] == nums[i - 1]: # 以出现的第一个a去找,肯定结果覆盖最广的,因为它的指针范围更广。所以判断前面有没有出现重复的a,出现了就跳过当前a
continue
# todo 2.2 剪枝:nums已经排过序了,如果nums[i]>0,那么直接终止本次循环
if nums[i] > 0:
break
# todo 2.3 开始找符合要求的数组
left = i + 1
right = len(nums) - 1
while left < right:
sum = nums[i] + nums[left] + nums[right]
# todo 2.3.1
# sum>0-->right 左移动;
# sum<0-->left 右移动;
# sum=0-->left,right 都向中间移动;
if sum > 0:
right -= 1
elif sum < 0:
left += 1
else:
res.append([nums[i], nums[left], nums[right]])
# 到这,已经出现了符合条件的结果,但是还有可能有其他组合,例如:nums = [-2,-1,-1,0,0,1,1,2] --> [[-2,0,2],[-2,1,1]]
# todo 2.3.2 指针去重
while right > 1 and nums[right] == nums[right - 1]:
right -= 1
while left < len(nums)-1 and nums[left] == nums[left + 1]:
left += 1
right -= 1
left += 1
return res
感想:
这题想清楚思路不难,难在去重,我是在写代码过程中,边写回过去补,一次写清楚所有去重不容易
首先是第一个数a的去重:a就是nums[i],如果以a开头已经找过一遍了,那么再以a开头去找,会产生重复数组,以出现的第一个a去找,肯定结果覆盖最广的,因为它的指针范围更广。所以判断前面有没有出现重复的a,出现了就跳过当前a
其次是第二个数b、第三个数c的去重:当第一次出现了该循环下符合条件的结果,但是还有可能有其他组合,例如:nums = [-2,-1,-1,0,0,1,1,2] --> [[-2,0,2],[-2,1,1]]。这个时候还不能跳出循环,还得继续向中间移动两个指针。但是,我们数组是排序过的,很有可能在左右指针附近还有相同的值,这都需要跳过去
18. 四数之和
题目:
给你一个由 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:
输入:nums = [1,0,-1,0,-2,2], target = 0
输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]示例 2:
输入:nums = [2,2,2,2,2], target = 8
输出:[[2,2,2,2]]
文章讲解:代码随想录 (programmercarl.com)
视频讲解:难在去重和剪枝!| LeetCode:18. 四数之和哔哩哔哩bilibili
思路:
这题思想与前一题一样,只是需要两层for循环,固定第一个数a,第二个数b,接下来思路与上一题一样,写的过程中也是要注意去重。
class Solution:
def fourSum(self, nums: list[int], target: int) -> list[list[int]]:
res = []
# todo 1.排序
nums.sort()
# todo 2.遍历数组定义指针
# todo 2.1 确定a
for i in range(len(nums)):
# todo a的去重(a就是nums[i]):
if i > 0 and nums[i] == nums[i - 1]:
continue
# todo 剪枝(可有可没有,减的不多)
# nums已经排过序了,如果target>0,且nums[i]>target,那么直接终止本次循环
if target > 0 and nums[i]> target:
break
# todo 2.2 确定b
for k in range(i + 1, len(nums)):
# todo b的去重(b就是nums[k]):
if k >= i + 2 and nums[k] == nums[k - 1]:
continue
# todo 剪枝(可有可没有,减的不多)
# nums已经排过序了,如果target>0,且nums[i]+nums[k]>target,那么直接终止本次循环
if target > 0 and nums[i] + nums[k] > target:
break
# todo 2.3 确定 c,d
left = k + 1
right = len(nums) - 1
while left < right:
sum = nums[i] + nums[k] + nums[left] + nums[right]
if sum > target:
right -= 1
elif sum < target:
left += 1
else:
res.append([nums[i], nums[k], nums[left], nums[right]])
# todo 左右指针去重
while right > 1 and nums[right] == nums[right - 1]:
right -= 1
while left < len(nums) - 1 and nums[left] == nums[left + 1]:
left += 1
right -= 1
left += 1
return res
感想:
说一说哈希表的选择吧
1)如果需要记录1个值,我们通常选set
2)如果需要记录两个值(比如值与下标,或值与出现的次数),我们通常选数组过字典
3)如果key的范围是有限的,且不多,可以用数组,开销小;如果key的范围不确定,还是用字典
从昨天到今天,可以很明显感受到哈希表降低复杂度,找出某个容器是否存在某数时,要想到哈希表
本文介绍了四道来自LeetCode的编程题目,涉及四数之和、三数之和和两个字符串相关问题。重点讲述了如何运用哈希表优化算法,如双指针法和哈希表查找,以降低时间复杂度。

1731





