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
- 为什么会想到用哈希表:本质上是计算某元素是否在集合里
- 哈希表为什么用字典:需要统计出现的次数,即value
- 本题字典是用来存什么的
- 字典中的key和value用来存什么的
这道题目是四个独立的数组,只要找到A[i] + B[j] + C[k] + D[l] = 0就可以,不用考虑有重复的四个元素相加等于0的情况
算法思想:遍历前两个数组,计算元素的和,放入字典,并统计次数。遍历后两个数组,计算元素和,看与之互补的元素和是否存在于record中,存在则加上这个互补和的次数。
注意⚠️:当在后两个数组中找到有互补元素和时,加的是record的value,record中有重复的和时,每一对都算是一个答案。
class Solution:
def fourSumCount(self, nums1: List[int], nums2: List[int], nums3: List[int], nums4: List[int]) -> int:
record = {}
ans = 0
# 计算前两个数组中元素的和,并放入字典
for n1 in nums1:
for n2 in nums2:
sum1 = n1+ n2
record[sum1] = record.get(sum1,0)+1
#计算后两个数组的和,判断0-sum2是否在字典中,注意在时加的是value不是1
for n3 in nums3:
for n4 in nums4:
sum2 = n3+n4
if 0-sum2 in record:
ans += record[0-sum2]
return ans
- 时间复杂度:O(n²)
- 空间复杂度:O(m)(取决于
nums1
和nums2
的和的种类数)
383. 赎金信
给你两个字符串:ransomNote
和 magazine
,判断 ransomNote
能不能由 magazine
里面的字符构成。
如果可以,返回 true
;否则返回 false
。
magazine
中的每个字符只能在 ransomNote
中使用一次。
要求只有小写字母,那么就给我们浓浓的暗示,用数组!
自己的方法1 数组:
这道题目和242.有效的字母异位词很像
class Solution:
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
cnt = [0]*26
for i in magazine:
cnt[ord(i)-ord('a')] +=1
for i in ransomNote:
cnt[ord(i)-ord('a')] -=1
if cnt[ord(i)-ord('a')] < 0:
return False
return True
时间复杂度
- 统计
magazine
字母:遍历magazine
,时间复杂度为 O(m),其中 m 是magazine
的长度。 - 检查
ransomNote
字母:遍历ransomNote
,时间复杂度为 O(n),其中 n 是ransomNote
的长度。 - 总时间复杂度:O(m + n)。
空间复杂度
- 计数数组
cnt
:固定大小的数组,空间复杂度为 O(1),因为它的大小是常数(26)。
自己的方法2 counter:
class Solution:
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
# 计算 magazine 中每个字母的统计
cnt1 = Counter(magazine)
# 计算 ransomNote 中每个字母的统计
cnt2 = Counter(ransomNote)
# 检查每个字母在 ransomNote 中的需求是否可以在 magazine 中满足
for char in cnt2:
if cnt1[char]<cnt2[char]:
return False
return True
- 时间复杂度:O(m + n)
- 空间复杂度:O(1)
改进:
class Solution:
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
return not Counter(ransomNote) - Counter(magazine)
Counter(ransomNote) - Counter(magazine)
进行减法操作,表示 ransomNote
中的字母需求减去 magazine
中的字母供给:
- 由于
magazine
中的字母足够满足ransomNote
中的需求,减法结果为空(Counter()
),表示没有剩余的需求。- 结果为空时,
not
会返回True
,表示可以构造;如果有剩余需求,返回False
。
- 结果为空时,
- 时间复杂度:O(m + n)
- 空间复杂度:O(1)
15. 三数之和
给你一个整数数组 nums
,判断是否存在三元组 [nums[i], nums[j], nums[k]]
满足 i != j
、i != k
且 j != k
,同时还满足 nums[i] + nums[j] + nums[k] == 0
。请你返回所有和为 0
且不重复的三元组。
注意:答案中不可以包含重复的三元组。
三个数都要去重导致用哈希很复杂-用双指针
去重细节
都是和 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} 这组数据一样可以收录到 结果集里。
1.两数之和 (opens new window),可不可以使用双指针法呢?
两数之和 就不能使用双指针法,因为1.两数之和 (opens new window)要求返回的是索引下标, 而双指针法一定要排序,一旦排序之后原数组的索引就被改变了。
如果1.两数之和 (opens new window)要求返回的是数值的话,就可以使用双指针法了。
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
nums.sort() #指针需要单调性
ans = []
for i in range(len(nums)-2): #留两个数和left,right指向的数据相加
# 如果第一个元素已经大于0,不需要进一步检查
if nums[i] > 0:
return ans
# 跳过相同的元素以避免重复
if i>0 and nums[i] == nums[i-1]:
continue
left = i+1
right = len(nums)-1
while left<right:
target = nums[i]+nums[left]+nums[right]
if target<0:
left+=1
elif target>0:
right-=1
else:
ans.append([nums[i],nums[left],nums[right]])
# 跳过相同的元素以避免重复
left+=1
while left < right and nums[left] == nums[left-1]:
left+=1
right -= 1
while left < right and nums[right] == nums[right+1]:
right-=1
return ans
时间复杂度
-
排序:
- 首先,我们对输入数组
nums
进行排序,时间复杂度为 O(n log n),其中 n 是数组的长度。
- 首先,我们对输入数组
-
外层循环:
- 外层循环遍历数组,最多进行 n 次迭代(从 0 到 n-3),复杂度为 O(n)。
-
内层双指针:
- 在每次外层循环中,内层的双指针操作(
left
和right
)最多会遍历剩余元素的次数。最坏情况下,内层循环的复杂度为 O(n)。 - 总体而言,内层指针操作在外层循环的每次迭代中最多执行 O(n) 次。
- 在每次外层循环中,内层的双指针操作(
结合所有部分,整体时间复杂度为:O(nlogn)+O(n2)=O(n2)
空间复杂度
-
存储结果:
- 结果列表
ans
中存储找到的三元组,空间复杂度为 O(k),其中 k 是结果中三元组的数量。
- 结果列表
-
排序的空间:
- 排序操作使用的额外空间为 O(1),因为是原地排序。
综合来看,整体空间复杂度为:O(k)
总结
- 时间复杂度:O(n²)
- 空间复杂度:O(k)
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
你可以按 任意顺序 返回答案 。
454.四数相加II (opens new window),相对于本题简单很多,因为本题是要求在一个集合中找出四个数相加等于target,同时四元组不能重复。
而454.四数相加II (opens new window)是四个独立的数组,只要找到A[i] + B[j] + C[k] + D[l] = 0就可以,不用考虑有重复的四个元素相加等于0的情况,所以相对于本题还是简单了不少!
class Solution:
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
nums.sort()
ans = []
# 第一个元素,去重
for a in range(len(nums)-3):
if a>0 and nums[a] == nums[a-1]:
continue
# 第二个元素,去重
for b in range(a+1,len(nums)-2):
if b > a+1 and nums[b] == nums[b-1]:
continue
# 双指针
c = b+1
d = len(nums)-1
while c<d:
s = nums[a] + nums[b] + nums[c] + nums[d]
if s < target:
c+=1
elif s>target:
d-=1
else:
ans.append([nums[a],nums[b],nums[c],nums[d]])
# 去重
c+=1
while c<d and nums[c]==nums[c-1]:
c+=1
d-=1
while c<d and nums[d]==nums[d+1]:
d-=1
return ans
- 时间复杂度: O(nlogn)+O(n)+O(n2)×O(n)=O(n3)
- 空间复杂度:O(k)
总结
字典:Counter()、defaultdict();数组; set()
字典变counter的方法用get()
- 小写字母 -- 数组计数(本质也是键值对),连续的键
- Counter()
区别:Counter()的键是元素的值
49. 字母异位词分组:排序后的字母异位词相同,将字符串加入排序后对应的键中
- defaultdict --自动创建缺失键的初始值
- set()适合没有限制数值的大小的情况
- 数组
- set() 去重
- 字典:比较数值,返回值为下标,对应着键值对
454.四数相加 (opens new window):类似两数和,返回值为次数
- 字典
383.赎金信:和242很像
- 数组:小写字母
- Counter()
18. 四数之和 (opens new window),15.三数之和
- 双指针:一个数组(集合)里找到和为0的组合&去重