代码随想录算法训练营Day6 | 454. 四数相加II,383. 赎金信,15. 三数之和,18. 四数之和

目录

454. 四数相加II

383. 赎金信

15. 三数之和

18. 四数之和


454. 四数相加II

题目链接:454. 四数相加 II - 力扣(LeetCode)

文章讲解:代码随想录

解题卡点:只想到大概思路,具体没想明白

        在集合中判断一个元素是否出现过哈希法。可以先遍历数组A和数组B,储存所有可能性在哈希表中,然后一一遍历数组C和数组D的组合,看0-(C+D)元素是否在哈希表中出现,如果出现,就找到了合适的元组。

        本题是1. 两数之和的升级版,由于是四个独立的数组,所以不需要考虑去重的操作。

        Tips:

        在CD数组的查找过程中,若使用索引dict[ ]去查找,普通字典会报错,默认字典会创建该元素的键值对影响字典,因此使用字典的get( )方法进行查找。

        字典获取值,dict.get(key, default=""),第一个参数为查找的键,第二个参数为键不存在时的返回内容。如果default没有设置,会返回None。

class Solution:
    def fourSumCount(self, nums1: List[int], nums2: List[int], nums3: List[int], nums4: List[int]) -> int:
        rec = collections.defaultdict(int)
        count = 0
        for i in nums1:
            for j in nums2:
                rec[i+j] += 1
        for i in nums3:
            for j in nums4:
                count += rec.get(0-i-j, 0)
                # count += rec[0-i-j] 错误写法,会导致字典中创建0-i-j的键值对
        return count

    # 时间复杂度: O(n^2)
    # 空间复杂度: O(n^2)
    # 最坏情况下前两个nums和后两个nums的值各不相同,相加产生的数字个数为n^2

383. 赎金信

题目链接:383. 赎金信 - 力扣(LeetCode)

文章讲解:代码随想录

解题卡点:无

        242. 有效字母异位词,相当于求字符串a和字符串b能否相互组成。383. 赎金信,相当于求字符串a能否由字符串b组成。

        使用一个长度为26的数组来记录magazine里字母出现的次数,再用ransomNote去验证这个数组是否包含了ransomNote所需要的所有字母。

        本题为数组在哈希法中的应用。另外在本题的情况下,使用map的空间消耗要比数组大一些的,因为map要维护红黑树或者哈希表,而且还要做哈希函数。数组更加简单直接有效。

class Solution:
    def canConstruct(self, ransomNote: str, magazine: str) -> bool:
        rec = [0]*26
        for i in magazine:
            rec[ord(i)-ord('a')] += 1
        for i in ransomNote:
            rec[ord(i)-ord('a')] -= 1
            if rec[ord(i)-ord('a')] < 0:
                return False
        return True

    # 时间复杂度 O(n)
    # 空间复杂度 O(1)

15. 三数之和

题目链接:15. 三数之和 - 力扣(LeetCode)

文章讲解:代码随想录

解题卡点:bc的去重没有想出来

        哈希解法:两个for循环确定两个数值,第三个数值用0-a-b代替,思路没有问题,但题目要求元素不重复,维护去重条件较为麻烦。

        双指针法:首先对列表进行排序,然后使用for循环确定a,且将a的下一个设为b,将列表的末尾设为c。sum=a+b+c。b向右移动sum不变或变大,c向左移动sum不变或变小。内层使用while循环,若sum小于0,b向右移动使sum变大,若sum大于0,c向左移动使sum变小,直至bc相遇。在bc相遇的过程中可能会有sum为0,记录此时的abc。

        一些小要点:

        剪枝:排序后的第一个元素a就是列表中最小的元素,若a大于0,则整体的sum一定大于0。因此对a进行判断,若大于0直接返回空列表。

        sum=0后如何移动bc:当sum=0时,右移b可能变大超过0,左移c可能变小小于0,只有同时让b右移和让c左移,才可能继续出现为0的情况。

        a的去重:a是nums里for循环遍历的元素,重复了直接跳过去,但必须是判断nums[i]与nums[i-1]是否相同,不能判断nums[i]与nums[i+1]是否相同。题目需要不重复的三元组,但元组内的元素可以重复。若使用nums[i]与nums[i+1],当a为第一个元素时,对于[-1,-1,2]这样的列表,会把第二个需要的-1也给跳过了。

        bc的去重:对于[0,-1,-1,-1,-1,2,2,2,2]这样的列表,在开始时a为0,b为第一个-1,c为最后一个2。随后进行while循环,b右移,c左移,依然能使sum=0,但元素重复。因此当sum=0之后,还需要判断b和b的右边是不是相同,c和c的左边是不是相同,若相同则继续移动相应指针。

        1. 两数之和可以使用双指针吗:不能使用双指针,因为1. 两数之和要求返回的是索引下标, 而双指针法一定要排序,一旦排序之后原数组的索引就被改变了。如果要求返回的是数值,则可以使用双指针法。

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:      
        res = []
        nums.sort() # 对列表排序,有序列表才能使用双指针
        for idx, a in enumerate(nums):
            if a > 0: # 提前剪枝
                return res
            if idx > 0 and a == nums[idx-1]: # 防止a重复
                continue
            idxb = idx+1
            idxc = len(nums)-1
            while idxb < idxc: # 直至bc相遇才退出循环
                b = nums[idxb]
                c = nums[idxc]
                if a+b+c > 0: # sum大则左移c使整体变小
                    idxc -= 1
                elif a+b+c < 0: # sum小则右移b使整体变大
                    idxb += 1
                else:
                    res.append([a, b, c])
                    while idxb < idxc and b == nums[idxb+1]: # 防止b重复
                        idxb += 1
                    while idxb < idxc and c == nums[idxc-1]: # 防止c重复
                        idxc -= 1
                    idxb += 1
                    idxc -= 1
        return res

    # 时间复杂度 O(n^2)
    # 空间复杂度 O(1)

18. 四数之和

题目链接:18. 四数之和 - 力扣(LeetCode)

文章讲解:代码随想录

解题卡点:无

        同15. 三数之和。两数之和双指针,三数之和外层套一个for循环,四数之和外层套两个for循环。

        剪枝时要注意,第一个元素大于0,则排序后整体sum单调递增,在第一个元素大于target且单调递增的情况下,后面元素的加入一定会使sum超过target,此时停止循环返回空列表。同理,在第二层剪枝中,先判断第一个元素是否大于0,是否单调递增,若前两个元素大于target,停止循环返回空列表。

class Solution:
    def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
        res = []
        nums.sort()
        for idxa in range(len(nums)):
            if nums[idxa] > target and nums[idxa] > 0: # 第一层剪枝
                break
            if idxa > 0 and nums[idxa] == nums[idxa-1]: # 防止a重复
                continue
            for idxb in range(idxa+1, len(nums)):
                if nums[idxa] + nums[idxb] > target and nums[idxa] > 0: # 第二层剪枝
                    break
                if idxb > idxa+1 and nums[idxb] == nums[idxb-1]: # 防止b重复
                    continue
                idxc = idxb+1
                idxd = len(nums)-1
                while idxc < idxd:
                    a = nums[idxa]
                    b = nums[idxb]
                    c = nums[idxc]
                    d = nums[idxd]
                    sum = a + b + c +d
                    if sum < target:
                        idxc += 1
                    elif sum > target:
                        idxd -= 1
                    else:
                        res.append([a, b, c, d])
                        while idxc < idxd and c == nums[idxc+1]: # 防止c重复
                            idxc += 1
                        while idxc < idxd and d == nums[idxd-1]: # 防止d重复
                            idxd -= 1
                        idxc += 1
                        idxd -= 1
        return res

    # 时间复杂度 O(n^3)
    # 空间复杂度 O(1)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值