DAY 7 哈希表part2

454. 四数相加 II

给你四个整数数组 nums1nums2nums3 和 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 != ji != 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

时间复杂度

  1. 排序

    • 首先,我们对输入数组 nums 进行排序,时间复杂度为 O(n log n),其中 n 是数组的长度。
  2. 外层循环

    • 外层循环遍历数组,最多进行 n 次迭代(从 0 到 n-3),复杂度为 O(n)。
  3. 内层双指针

    • 在每次外层循环中,内层的双指针操作(left 和 right)最多会遍历剩余元素的次数。最坏情况下,内层循环的复杂度为 O(n)。
    • 总体而言,内层指针操作在外层循环的每次迭代中最多执行 O(n) 次。

结合所有部分,整体时间复杂度为:O(nlogn)+O(n2)=O(n2)

空间复杂度

  1. 存储结果

    • 结果列表 ans 中存储找到的三元组,空间复杂度为 O(k),其中 k 是结果中三元组的数量。
  2. 排序的空间

    • 排序操作使用的额外空间为 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
  • abc 和 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()



242. 有效的字母异位词 

  • 小写字母 -- 数组计数(本质也是键值对),连续的键
  • Counter()

区别:Counter()的键是元素的值

49. 字母异位词分组:排序后的字母异位词相同,将字符串加入排序后对应的键中

  • defaultdict --自动创建缺失键的初始值

 349. 两个数组的交集

  • set()适合没有限制数值的大小的情况
  • 数组 

202. 快乐数

  • set() 去重

1. 两数之和

  • 字典:比较数值,返回值为下标,对应着键值对

454.四数相加 (opens new window):类似两数和,返回值为次数

  • 字典

383.赎金信:和242很像

  • 数组:小写字母
  • Counter()

18. 四数之和 (opens new window)15.三数之和

  • 双指针:一个数组(集合)里找到和为0的组合&去重

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值