力扣Day7(1.31)| 第三章 哈希表 ( 454. 四数相加II 383. 赎金信 题三:15. 三数之和 18. 四数之和 )

题一:第454题.四数相加II

2023/1/30 22:00

链接

题目链接:
视频链接:
文章链接:

视频总结

关键点

  1. 思路:分成两组,分别遍历相加之后然后再对两部分采用哈希法
  2. 注意点:数值很大,数组不可用,只能用set或者map,而哈希表需要储存和值和次数,而set只能储存是否出现过这一信息,所以用map,而python里都是用字典来实现

编程思路

Me:

只想到了暴力解法,恩的四方复杂度

卡尔:

  1. 对一二数组元素遍历相加,结果和出现的次数储存在哈希表里,key是和值,value是次数
  2. 对三四数组元素遍历相加,结果的负数判断是否在哈希表中,若在,最终的计数加上哈希表中那个数对应的value,也就是次数
  3. 最终返回总计数

力扣实战

思路一:

 class Solution:
    def fourSumCount(self, nums1: List[int], nums2: List[int], nums3: List[int], nums4: List[int]) -> int:
        count = 0       # 记录最终元组的个数
        hash = {}
        for i in nums1:     # 第一个for循环建立哈希表,key为一二数组任意两个数字和的结果,value是结果的次数
            for j in nums2:
                if i + j not in hash:
                    hash[i+j] = 1
                else:
                    hash[i+j] +=1
        for i in nums3:     # 第二个for循环先对三四数组元素遍历相加,然后判断结果的相反数是否在哈希表中,若在则计数count加上key为相反数时对应的value
            for j in nums4:
                if -(i+j) in hash:
                    count += hash[-(i+j)]
        return count

        
# 反思1:

文档总结

1. 注意调用collections的写法

题二:383. 赎金信

链接

题目链接:
文章链接:

编程思路

Me:
  1. 用数组即可拿下,和day6的第一题很像,ord函数获得字符串地址,遍历字符串一时建立哈希表,次数加一,遍历字符串二时次数减一,最后遍历values,若有值为1则不是,负责是。
卡尔:

退出条件书写时发生了错误,不只等于一时可以退出,也可以是其他的正数。

力扣实战

思路一:

 class Solution:
    def canConstruct(self, ransomNote: str, magazine: str) -> bool:
        hash = [0]*26
        for i in ransomNote:
            hash[ord(i)-ord("a")] += 1
        for j in magazine:
            hash[ord(j)-ord("a")] -= 1
        # if 1 in hash:       # 也可能是2 3 4...
        #     return False
        # else:
        #     return True
        for i in hash:
            if i>0:
                return False
        return True
        
# 反思1:写法可以优化,第二个for循环可以直接开始判断!思路类似于经典的map法
class Solution:
    def canConstruct(self, ransomNote: str, magazine: str) -> bool:

        arr = [0] * 26

        for x in magazine:    # 记录 magazine里各个字符出现次数
            arr[ord(x) - ord('a')] += 1

        for x in ransomNote:  # 在arr里对应的字符个数做--操作
            if arr[ord(x) - ord('a')] == 0:  # 如果没有出现过直接返回
                return False
            else:
                arr[ord(x) - ord('a')] -= 1
        
        return True

题三:15. 三数之和

链接

题目链接:
视频链接:
文章链接:

视频总结

关键点

  1. 去重细节

编程思路

Me:
  1. 只想到了暴力法,三层for循环找出所有可能,再判断是否为零
卡尔:
  1. 用传统的map法,分成两组,第一组建立哈希表,两个for循环,但是因为实在一个数组上需要去重,所以用哈希法比较繁琐,去重细节较多
  2. 双指针法较好,但是也要注意去重环节。continue语句的作用是跳过本次循环体中余下尚未执行的语句,立即进行下一次的循环条件判定,可以理解为仅结束本次循环。
  3. 边界问题:r>l还是>=,遇到边界问题时可以采用假设带入法,若等于时,则两个数一样,而我们要求的是一个三元组。故只能写大于
  4. 去重问题:同一个二元组,如[-3,2,1]可能会出现多次
    所以在排序之后,第一个for循环里首次去重,发现和前一个数一样,就可以直接结束这次循环,跳到下一次循环里。
  5. 第二次去重是在移动l和r指针时,此时也可能会出现重复的元祖,同样r指针若和前一个数相等,说明r需要向后再移动一个数(因为和前一个相等时,第三个数就已经是确定了,l指针无论怎么移动,一定会和此前的元组相同)
  6. 去重逻辑要保证至少先获得一个元组结果,所以不可放在while条件里,否则当数组元素全为0时,一个结果也没有

力扣实战

思路一:

 class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        res = []
        nums.sort()  # 数组排序
        for i in range(len(nums)):
            l = i + 1   # 定义左右指针
            r = len(nums)-1
            if nums[i]>0:   #若a大于0,则bc大于0,abc之和不可能为0,故结束此次循环
                continue
            elif i>0 and nums[i]==nums[i-1]: # 对a去重 和前一个比较,而不是后一个
                continue
            while l<r:  # 边界问题,只能是小于,不能是≤,因为b的索引不能等于c的索引
                if nums[i] +nums[l] +nums[r] ==0:
                    res.append([nums[i],nums[l],nums[r]])
                    l +=1
                    r -=1
                    # if nums[l]==nums[l-1]:  #对b和c去重
                    #     l +=1
                    # elif nums[r]==nums[r+1]:
                    #     r -=1
                    # while nums[l]==nums[l-1]:  #对b和c去重 #错误:指针越限
                    #     l +=1
                    # while nums[r]==nums[r+1]:
                    #     r -=1
                    while l<r and nums[l]==nums[l-1]:  #对b和c去重 #错误:指针越限
                        l +=1
                    while l<r and nums[r]==nums[r+1]:
                        r -=1
                elif nums[i] +nums[l] +nums[r] >0:  #移动左右指针
                    r -= 1
                else:
                    l +=1
        return res
        
# 反思:编程时出现两次错误,都是在去重时,第一次是对bc去重,用了if语句,导致只做了一次去重,去重不彻底应该改用while;第二次错误是while条件没加l<r,导致指针最后越界了

思路二:

 # 哈希表法代码简洁的多,去除了繁琐的去重操作
        hashmap = dict()
        target = 0
        for n in nums: # 建立哈希表,key为数值 value为数值在数组中出现的次数
            if n in hashmap:
                hashmap[n] += 1
            else: 
                hashmap[n] = 1
        ans = set()     
        for i in range(len(nums)):
            for j in range(i + 1, len(nums)):   # 哈希也是把暴力的恩三方减少到恩平方的时间复杂度,在第二层循环里,用前两个数表示第三个数,然后在哈希表里查询是否存在,从而在这里用空间换取了时间
                val = target - (nums[i] + nums[j]) 
                if val in hashmap:
                    count = (nums[i] == val) + (nums[j] == val) #计数原理和四数值和里思路一致,每一个括号里都为bool的True或者False,但他们加减运算时被视为0或1
                    if hashmap[val] > count:
                        ans_tmp = tuple(sorted([nums[i], nums[j], val]))
                        ans.add(ans_tmp)
                    else:
                        continue
        return list(ans) # 改进思路,直接使用数组来存储,不需要调用内部的函数或许更快×××,数组操作则需要大量去重,如
        #把[[-1,0,1],[-1,1,0],[-1,2,-1],[-1,-1,2],[0,1,-1],[0,-1,1],[1,-1,0],[2,-1,-1]]去重为[[-1,-1,2],[-1,0,1]]
        # 而set为什么可以呢?这个问题有待研究一下,研究思路:研究set函数的用法

文档总结

1. 第一个continue最好改成break,表示这个a>0后,那么后面的循环都不要考虑了,循环终止。而continue只是结束了本轮循环,虽然在后面的循环里,a全部都是大于零,但是执行的时间白白增加了
2. 对bc去重有两种写法,先自加再去重,或者先去重再自加

题四:18. 四数之和

链接

题目链接:
视频链接:
文章链接:

视频总结

编程思路

Me:
  1. 排序,然后第一层for循环遍历数组,然后子问题变为三数之和等于target-nums[i]。(漏掉了剪枝的操作,最后耗时会长一点)
卡尔:
  1. 排序,遍历数组,第一个参数i对应a
  2. 剪枝,即确认部分情况可以直接终止本次及后面的循环,即break
  3. 去重对a
  4. 对b的参数j遍历,从i+1开始
  5. 剪枝去重
  6. 再进行左右指针的操作,和三数之和类似

力扣实战

思路一:

 class Solution:
    def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
        # 把四数之和想象成a+b+c+d
        nums.sort()     # 数组排序,便于移动l和r
        n = len(nums)
        res = []        #存放四元组数组
        for i in range(n):  # 外层遍历数组,针对参数a
            if nums[i]>target and nums[i]>0: # 剪枝操作:此时后面的数一定不满足要求,无论bcd是什么,所以结束本轮和后面的循环
                break
            elif i>0 and nums[i]== nums[i-1]: # 去重操作,和前一个比,因为i的初始是0,所以从0+1才开始考虑去重
                continue
            for j in range(i+1,n): # 内存遍历i后面的数组元素,针对参数b
                if nums[i]+nums[j]>target and nums[j]>0:    #剪枝:注意是i+j,这样的范围比j更大,剪掉的更多
                    break
                elif j>i+1 and nums[j] == nums[j-1]: # 去重:注意起始处是i+1!而不是0,如果是零的话,b的j有可能和a的i相同,导致去掉了不该去的部分
                    continue
                l = j+1     #其余的大体和三数之和一致
                r = n-1
                while l<r:
                    if nums[i]+nums[j]+nums[l]+nums[r]>target:
                        r -= 1
                    elif nums[i]+nums[j]+nums[l]+nums[r]<target:
                        l +=1
                    else:
                        res.append([nums[i],nums[j],nums[l],nums[r]])
                        while l<r and nums[l] == nums[l+1]:
                            l += 1
                        while l<r and nums[r] == nums[r-1]:
                            r -= 1
                        l +=1
                        r -=1
        return res
        
# 总结1:第二层for循环的剪枝和去重小细节很有意思

文档总结

1. 剪枝,逻辑变成nums[i] > target && (nums[i] >=0 || target >= 0)就可以了
2. 三数之和的时间复杂度是O(n^2),四数之和的时间复杂度是O(n^3)3. 一样的道理,五数之和、六数之和等等都采用这种解法
4. 对于三数之和 双指针法就是将原本暴力O(n^3)的解法,降为O(n^2)的解法,四数之和的双指针解法就是将原本暴力O(n^4)的解法,降为O(n^3)的解法。
5. 双指针法将时间复杂度:O(n^2)的解法优化为 O(n)的解法。也就是降一个数量级

思路二

# 哈希表法
class Solution(object):
    def fourSum(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: List[List[int]]
        """
        # use a dict to store value:showtimes
        hashmap = dict()
        for n in nums:
            if n in hashmap:
                hashmap[n] += 1
            else: 
                hashmap[n] = 1
        
        # good thing about using python is you can use set to drop duplicates.
        ans = set()
        # ans = []  # save results by list()
        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 hashmap:
                        # make sure no duplicates.
                        count = (nums[i] == val) + (nums[j] == val) + (nums[k] == val)
                        if hashmap[val] > count:
                          ans_tmp = tuple(sorted([nums[i], nums[j], nums[k], val]))
                          ans.add(ans_tmp)
                          # Avoiding duplication in list manner but it cause time complexity increases
                          # if ans_tmp not in ans:  
                          #     ans.append(ans_tmp)
                        else:
                            continue
        return list(ans) 
      #很有意思的哈希表思路,关键步骤是count和value比大小!且value必须大于count,若等于,说明有一个数被使用了两次,若是小于,说明有一个数至少被使用了三次。使用哈希表的写法就直接不需要考虑去重的操作了,这是优点!按这样算的话,三数之和也是可以使用哈希表法的

2023/1/31 16:41

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值