算法打卡Day06

本文介绍了四道来自LeetCode的编程题目,涉及四数之和、三数之和和两个字符串相关问题。重点讲述了如何运用哈希表优化算法,如双指针法和哈希表查找,以降低时间复杂度。

今日任务:

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. 赎金信

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

题目:
给你两个字符串: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. 三数之和

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

题目:
给你一个整数数组 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. 四数之和

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

题目:
给你一个由 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的范围不确定,还是用字典

从昨天到今天,可以很明显感受到哈希表降低复杂度,找出某个容器是否存在某数时,要想到哈希表

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值