代码随想录算法训练营第六天|454.四数相加II、383. 赎金信、15. 三数之和、18. 四数之和、总结

写代码的第六天

454.四数相加II

自己的思路使用map(但是仔细看好像和哈希没关系)

为什么使用map?题中给的数组长度范围很大,如果用数组存储两数相加的和那么数组长度不可控,所以不用数组;为什么不用集合set是因为使用set再添加数字的时候,重复的是不可以添加的,如果位置不同的两个数相加的结果是相通的,那么这个数字是无法加入的,也就导致这两个数字的本来可以和其他数字构成新的答案,在set中却不算,并且我们需要两个变量的同时存储,分别是两数相加的和以及这两个数对应的下标位置,所以使用map更好。
首先遍历nums1和nums2,计算这两个数组所有两数相加的和,存放进字典中,然后遍历nums3和nums4,计算这两个数组所有两数相加的和,存放进字典中。在这个字典中我们是要根据两数之后来确定最后的下标,所以用keys存放数值,用value存放下标。最后比较这两个字典中的key值,如果有相加为零的,那么输出对应的value。
错误代码一:
问题1:我这个字典设置的是空字典,想返回的是每个满足条件的ijkl,但是题中给的是只输出最后有多少个就可以。但是会发现在字典中key值不变,改变的是value,也就是说如果key为1的时候,下标为(0,1)的和(1,2)的都能满足和为-1,那么这个时候value值应该是多少呢?这个是无法确定的,所以在这里我们将字典初始化为零,然后可以根据key值对相应的value进行累加,如果还是上述的例子,那么这个时候key和value就是(-1,2),值为-1,两种组合。
问题2:如果a+b==0的时候根据a和b对应的value来计算有多少个组合方式,这个时候不仅仅是count+=1就可以了,因为每两个数组中的数值求和并不是一个和只出现一次,有可能出现多次,所以说不能用count+=1
问题3:如果说nums1和nums2中有一个和为1出现了两次,那么字典中表示的就是(1,2),如果nums3和nums4中有一个和为-1出现了三次,那么他们两个和为0的下标组合是2*3个。

class Solution:
    def fourSumCount(self, nums1: List[int], nums2: List[int], nums3: List[int], nums4: List[int]) -> int:
        dict1 = {}
        dict2 = {}
        count = 0
        for i in range(len(nums1)):
            for j in range(len(nums2)):
                x = nums1[i] + nums2[j]
                dict1[x] = [i,j]
        for k in range(len(nums3)):
            for l in range(len(nums4)):
                x = nums3[k] + nums4[l]
                dict2[x] = [k,l]
        for a in dict1.keys():
            for b in dict2.keys():
                if a+b == 0:
                    count += 1
        return count 

正确代码:

from collections import defaultdict
class Solution:
    def fourSumCount(self, nums1: List[int], nums2: List[int], nums3: List[int], nums4: List[int]) -> int:
        dict1 = defaultdict(int)
        dict2 = defaultdict(int)
        count = 0
        for i in range(len(nums1)):
            for j in range(len(nums2)):
                x = nums1[i] + nums2[j]
                dict1[x] += 1
        for k in range(len(nums3)):
            for l in range(len(nums4)):
                x = nums3[k] + nums4[l]
                dict2[x] += 1
        for a in dict1.keys():
            for b in dict2.keys():
                if a + b == 0:
                    m = dict1[a] * dict2[b]
                    count += m
        return count 

正经的哈希实现

使用map,和之前说的原因一样。
思路:首先遍历数组1和2,计算所有出现的和并存储在字典中,key存放和,value存放出现的次数。然后遍历数组3和4,只需要判断这两个数组中出现的和的相反数有没有在字典中出现,如果出现了,那么直接把对应的值的value加到最后输出个数的count中(上面用乘法是因为我把12数组和34数组分别都计算出来了,所以用乘法,在这里吗我只计算了12数组,34数组是计算一个找一个,不是全都计算出来)。
正确代码:

class Solution:
    def fourSumCount(self, nums1: List[int], nums2: List[int], nums3: List[int], nums4: List[int]) -> int:
        dict1 = {}
        result = 0
        for i in range(len(nums1)):
            for j in range(len(nums2)):
                x = nums1[i] + nums2[j]
                if x in dict1:
                    dict1[x] += 1
                else:
                    dict1[x] = 1
        for k in range(len(nums3)):
            for l in range(len(nums4)):
                if -(nums3[k] + nums4[l]) in dict1.keys():
                    result += dict1[-(nums3[k] + nums4[l])]
        return result

383. 赎金信

哈希数组实现

**思路:**题中给了约束两个字符串都是小写组成,所以可以用数组实现哈希。先遍历magazine字符串,把0-26的数组下标代表a-z的小写字母,下标中出现的数字代表每个字母出现的次数;然后遍历ransomNote字符串,如果在上述哈希数组中出现对应的字母,那么就把这个字母对应的数值-1;遍历结束之后,如果哈希数组中有负数出现,那么返回false,否则返回true。
错误版本一:误以为只有当字符串r中字符在哈希数组中出现过才-1.但是我们最后要对比的是有没有出现负数,所以不论字符串r有没有在哈希数组中出现过都要-1.

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

正确代码

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

哈希map实现

(我自己的理解)为什么没用set集合实现?因为我觉得在这个题中涉及到计数的问题,但是set是没办法计数的,甚至重复的数字都是不能出现的,而且题干中要求杂志字符串中的每个字符只能在赎金信字符串中使用一次。所以set不行。(如果有可以实现的大佬们,麻烦指正,谢谢)
思路:首先遍历magazine字符串,把里面出现的字符和次数分别存在keys和values中,然后再遍历r字符串,与字典比较的key进行比较,如果有相等的就-1,如果遍历结束后r中的有些值还是没有在字典中找到,那么直接返回false,最后在遍历字典,如果字典中有的value为负数,那么返回false。
错误第一版:错误原因是,我只考虑了在哈希map中对value不断加一,但是没考虑到如果这个字符是第一次出现,这个+1就非法了,所以要加入一个判断,如果这个字符是第一次出现那么赋值为1,如果不是的话就+1.

class Solution:
    def canConstruct(self, ransomNote: str, magazine: str) -> bool:
        hashdict = {}
        for i in magazine:
            hashdict[i] += 1
        for j in ransomNote:
            if j in hashdict:
                hashdict[j] -= 1
            if j not in hashdict:
                return False
        for i in hashdict.keys():
            if hashdict[i] < 0:
                return False
        return True

报错信息:
在这里插入图片描述
正确代码

class Solution:
    def canConstruct(self, ransomNote: str, magazine: str) -> bool:
        hashdict = {}
        for i in magazine:
            if i in hashdict:
                hashdict[i] += 1
            else:
                hashdict[i] = 1
        for j in ransomNote:
            if j in hashdict:
                hashdict[j] -= 1
            if j not in hashdict:
                return False
        for i in hashdict.keys():
            if hashdict[i] < 0:
                return False
        return True

15. 三数之和

双指针法

思路:从数组下标为0的位置开始遍历,这个值设置为a,然后将双指针left和right分别设置为a这个值的后一位和数组的最后一位,此时left位置就是b,right位置就是c;这个时候判断a+b+c是否为0,如果是就将结果(nums[i],nums[left],nums[right])append到最后输出的result中。
错误第一版
1、错误原因是数组越界了,i的范围是0~len(nums)-1,left=i+1,如果现在i在数组最末尾,这个时候left就越界了。
2、还存在一个问题,我只判断了和为0的情况,和不为零的时候呢?当和大于零的时候right指针-1,当和小于零的时候left指针+1。
3、所有对数组的操作都是基于这个数组要是有序的情况,才能进行加加减减判断操作,所以第一步应该进行排序。

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        result = []
        for i in range(len(nums)):
            left = i + 1
            right = len(nums) - 1
            if (nums[i] + nums[left] + nums[right] == 0):
                result.appenf([nums[i],nums[left],nums[right]])
        return result

在这里插入图片描述
错误第二版:解答错误。我们根据这个例子重新看一下这段代码,会发现当和小于零和大于零的时候left和right指针都是有变化的,但是当和为0的时候指针就不动了,这是不对的,left和right指针应该是不停的向中间移动,所以left指针继续+1,而right指针继续-1。

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        nums = sorted(nums)
        result = []
        for i in range(len(nums)):
            left = i + 1
            right = len(nums) - 1
            if left < right and nums[i] + nums[left] + nums[right] > 0:
                right -= 1
            elif left < right and nums[i] + nums[left] + nums[right] < 0:
                left += 1
            elif left < right and nums[i] + nums[left] + nums[right] == 0:
                result.append([nums[i],nums[left],nums[right]])
        return result

在这里插入图片描述
错误第三版:还是这个错误,又重新看了代码逻辑,发现在for循环中的if语句,只执行一次!!!!!!只有涉及到与其相关的条件的时候,只执行一次,后续不在执行,也就是left或者right一次循环只执行一次!!!!并没有像想象中的循环执行!!!!!

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        nums = sorted(nums)
        result = []
        for i in range(len(nums)):
            left = i + 1
            right = len(nums) - 1
            if left < right and nums[i] + nums[left] + nums[right] > 0:
                right -= 1
            elif left < right and nums[i] + nums[left] + nums[right] < 0:
                left += 1
            elif left < right and nums[i] + nums[left] + nums[right] == 0:
                result.append([nums[i],nums[left],nums[right]])
                left += 1
                right -= 1
        return result

在这里插入图片描述
错误第四版:错误原因是提干中给了答案中不可以包含重复的三元组。也就是说三元组里面的数字可以是一样的,但是不能出现完全一致的三元组。开始去重。一共有abc三个数字,也就是说abc三个数字都涉及到去重。

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        nums = sorted(nums)
        result = []
        for i in range(len(nums)):
            left = i + 1
            right = len(nums) - 1
            while left < right:
                if nums[i] + nums[left] + nums[right] > 0:
                    right -= 1
                elif nums[i] + nums[left] + nums[right] < 0:
                    left += 1
                elif nums[i] + nums[left] + nums[right] == 0:
                    result.append([nums[i],nums[left],nums[right]])
                    left += 1
                    right -= 1
        return result

在这里插入图片描述
解决问题1:如何对数字a去重?如果a和其后面的一个数是相等的,那么就不再对a后面的这个数进行找和为零的操作。解释一下为什么:比如现在的数组是[-1,-1,0,1,2,4],for循环i当前位置在下标为i=0,此时a=-1处,我们在这个位置进行上面代码求和判断操作得到了[-1,0,1]这个三元组和为零,并且left和right的遍历范围是[-1,0,1,2,4];然后i继续向下遍历,现在i=1,此时a还是-1,那么在这个位置继续操作求和等步骤得到的结果还有[-1,0,1],并且left和right的遍历范围是[0,1,2,4],所以会发现left和right遍历的范围下面的是上面的子集,也就是说刨除了和自身数字相等的那个数字之外,剩下的元素是相等的,也就是说left和right始终能用到的都是这几个数,这个时候这两个三元组就是重复的,就需要去重,留一个就行。所以遇到其后面一个数值和前一个数值相等的时候,直接continue,但是会不会有丢失满足条件的三元组的情况?其实不会,因为在遇到第一个-1的时候所有和-1相关能组合成0的方式已经都遍历完成了,后面在遇到-1能进行的操作也只是在后面的子集上进行操作,得到的结果被包含在之前得到的结果中。

if i > 0 and nums[i-1] == nums[i]:
    continue

解决问题2:如何对数字b去重?b也就是nums[left],可以想这样一个极端情况[-1,0,0,0,1],那么也就是说当left对应的值和后面一位的值一致的时候continue,所以在这里我们要用while而不是用if。
解决问题3:如何对数字c去重?c也就是nums[right],可以想这样一个极端情况[-1,-1,0,1,1],那么也就是说当right对应的值和前面一位的值一致的时候continue,所以在这里我们要用while而不是用if。
解决问题4:关于b和c的去重应该放在哪里?既然是去重,那肯定要是已经满足条件了,出现重复的情况了才能去重,所以应该放在和等于0的这段if语句里面,并且应该放在append之后,因为我们要先存储一个合理的值,再把剩下的去重。
错误第五版:虽然外层有 left < right 的限制,但是在内部循环的时候,仍然可能出现 left 一直 ++ 直到越界的情况,它只在内部这个循环移动不会到外部去判断嘛,所以最上面那个条件此时是不管用的。建议所有的类似操作都在前面加防止越界的条件。

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        nums = sorted(nums)
        result = []
        for i in range(len(nums)):
            if i > 0 and nums[i-1] == nums[i]:
                continue
            left = i + 1
            right = len(nums) - 1
            while left < right:
                if nums[i] + nums[left] + nums[right] > 0:
                    right -= 1
                elif nums[i] + nums[left] + nums[right] < 0:
                    left += 1
                elif nums[i] + nums[left] + nums[right] == 0:
                    result.append([nums[i],nums[left],nums[right]])
                    while nums[left+1] == nums[left]:
                        left += 1
                    while nums[right-1] == nums[right]:
                        right -= 1
                    left += 1
                    right -= 1
        return result

在这里插入图片描述
正确代码

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        nums = sorted(nums)
        result = []
        for i in range(len(nums)):
            if i > 0 and nums[i-1] == nums[i]:
                continue
            left = i + 1
            right = len(nums) - 1
            while left < right:
                if nums[i] + nums[left] + nums[right] > 0:
                    right -= 1
                elif nums[i] + nums[left] + nums[right] < 0:
                    left += 1
                elif nums[i] + nums[left] + nums[right] == 0:
                    result.append([nums[i],nums[left],nums[right]])
                    while left < right and nums[left+1] == nums[left]:
                        left += 1
                    while left < right and nums[right-1] == nums[right]:
                        right -= 1
                    left += 1
                    right -= 1
        return result

18. 四数之和

双指针

思路:在上面的三数之和外面再套一个循环用来遍历第四个数,然后把原始三数之和里面的和为零,变为target-第四个数。
错误代码第一版:在判断以下这种情况的时候会输出空。也就是说在判断连续相同的数字会出现不进入数组的情况,所以推断可能是continue掉了,将continue这段语句移到最外层循环中,因为我们要判断的是四个数中,第一个数之后有没有和这个数相等的,所以应该在第一层for循环之后就if判断。

class Solution:
    def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
        nums = sorted(nums)
        result = []
        for four in range(len(nums)):
            for i in range(four+1,len(nums)):
                if i > 0 and nums[i-1] == nums[i]:
                    continue
                left = i + 1
                right = len(nums) - 1
                while left < right:
                    if nums[four] + nums[i] + nums[left] + nums[right] > target:
                        right -= 1
                    elif nums[four] + nums[i] + nums[left] + nums[right] < target:
                        left += 1
                    else:
                        result.append([nums[four],nums[i],nums[left],nums[right]])
                        while left < right and nums[left] == nums[left + 1]:
                            left += 1
                        while left < right and nums[right] == nums[right - 1]:
                            right -= 1
                        left += 1
                        right -= 1
        return result

在这里插入图片描述
错误代码第二版:看报错的测试用例会发现没去重。也就证明对于abcd四个数的去重代码是不对的,重新看代码逻辑,一共是四个数,所以证明要对abcd分别做去重,那么其实就是在上题的基础上再对第四个数进行去重,去重的规则和上面所说的三数之和最外层的是一样的。

class Solution:
    def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
        nums = sorted(nums)
        result = []
        for four in range(len(nums)):
            if four > 0 and nums[four-1] == nums[four]:
                    continue
            for i in range(four+1,len(nums)):
                left = i + 1
                right = len(nums) - 1
                while left < right:
                    if nums[four] + nums[i] + nums[left] + nums[right] > target:
                        right -= 1
                    elif nums[four] + nums[i] + nums[left] + nums[right] < target:
                        left += 1
                    else:
                        result.append([nums[four],nums[i],nums[left],nums[right]])
                        while left < right and nums[left] == nums[left + 1]:
                            left += 1
                        while left < right and nums[right] == nums[right - 1]:
                            right -= 1
                        left += 1
                        right -= 1
        return result

在这里插入图片描述
正确代码

class Solution:
    def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
        nums = sorted(nums)
        result = []
        for four in range(len(nums)):
            if four > 0 and nums[four-1] == nums[four]:
                    continue
            for i in range(four+1,len(nums)):
                if i > four+1 and nums[i-1] == nums[i]:
                    continue
                left = i + 1
                right = len(nums) - 1
                while left < right:
                    if nums[four] + nums[i] + nums[left] + nums[right] > target:
                        right -= 1
                    elif nums[four] + nums[i] + nums[left] + nums[right] < target:
                        left += 1
                    else:
                        result.append([nums[four],nums[i],nums[left],nums[right]])
                        while left < right and nums[left] == nums[left + 1]:
                            left += 1
                        while left < right and nums[right] == nums[right - 1]:
                            right -= 1
                        left += 1
                        right -= 1
        return result

总结

1、defaultdict(int) 是 Python 中 collections 模块中的一个类,它是字典(dict)的一个子类。与普通的字典不同,defaultdict 在初始化的时候需要提供一个默认值的类型作为参数。在这个例子中,int 被作为默认值类型传递给 defaultdict。当你使用 defaultdict 创建一个字典时,如果你访问一个不存在的键,它不会抛出 KeyError 异常,而是自动返回一个默认值。对于 int 类型的默认值,它将返回 0。
2、在Python的标准字典(dict)中,是不允许出现重复的键(key)的。每个键在字典中必须是唯一的,如果你尝试使用相同的键进行多次赋值,后续的赋值会覆盖前面的赋值。当你使用相同的键进行赋值时,字典会保留最后一次赋的值。

  • 30
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值