代码随想录第七天:第454题.四数相加II、383. 赎金信、第15题. 三数之和、第18题. 四数之和

Day7,3h,补了一下昨天的……

454.四数相加II

给定四个包含整数的数组列表 A , B , C , D ,计算有多少个元组 (i, j, k, l) ,使得 A[i] + B[j] + C[k] + D[l] = 0。

为了使问题简单化,所有的 A, B, C, D 具有相同的长度 N,且 0 ≤ N ≤ 500 。所有整数的范围在 -2^28 到 2^28 - 1 之间,最终结果不会超过 2^31 - 1 。

自己写

A+b+c+d=0,先理解成(a+b)=-(c+d),因为题目只是求有多少个符合条件的元组,那我需要一个地方存储a+b的所有值,还有出现的次数;c+d同理,然后遍历两个表,使得量表项中值相加为0,然后出现的次数相乘(组合操作)

其中涉及到通过值寻找次数的操作,所以使用的数据结构应该是键-值,key为相加值,value为次数

class Solution(object):
    def fourSumCount(self, nums1, nums2, nums3, nums4):
        ab = {}
        cd = {}
        n = len(nums1)
        for i in range(n):
            for j in range(n):
                numab = nums1[i] + nums2[j]
                numcd = nums3[i] + nums4[j]
                if numab not in ab.keys():
                    ab[numab] = 1
                else:
                    ab[numab] += 1
                if numcd not in cd.keys():
                    cd[numcd] = 1
                else:
                    cd[numcd] += 1
        count = 0
        for key in ab.keys():
            if (0-key) in cd.keys():
                count += ab[key] * cd[0-key]
        return count

代码写了,超时了……用例通过130/132,卡在一个n比较大的用例上

看了一下题解,发现自己还是在犯了前几天一样的错误

非必要,不创建新的变量。不仅是因为变量占用空间,更是因为变量的冗余也会导致计算过程的冗余。

必须要创建ab和cd分别用于存储a+b和c+d的计算和吗?如果只用一个数组存储,遍历计算a+b是存进结果,遍历计算c+d时进行比较和结果的处理,即省去了一个变量的同时,也省去了上文遍历字典ab、cd的过程。

看题解才发现python按key字典可以直接写if num in dict……

class Solution(object):
    def fourSumCount(self, nums1, nums2, nums3, nums4):
        d = {}
        for n1 in nums1:
            for n2 in nums2:
                n12 = n1 + n2
                if n12 in d:
                    d[n12] += 1
                else:
                    d[n12] = 1

        count = 0
        for n3 in nums3:
            for n4 in nums4:
                n34 = n3 + n4
                if (0-n34) in d:
                    count += d[(0-n34)]
        return count

383. 赎金信

给定一个赎金信 (ransom) 字符串和一个杂志(magazine)字符串,判断第一个字符串 ransom 能不能由第二个字符串 magazines 里面的字符构成。如果可以构成,返回 true ;否则返回 false。

(题目说明:为了不暴露赎金信字迹,要从杂志上搜索各个需要的字母,组成单词来表达意思。杂志字符串中的每个字符只能在赎金信字符串中使用一次。)

跟前几天那个242.有效的字母异位词是完全一样的……于是进行一个复刻,在讨论242题的时候已经讨论过,由于这道题限定于26个小写字母,相比于使用字典,用数组自建哈希是更明智的,用时更短。

参考代码随想录的示例版本,来学习一下return的思路,不用使用遍历去进行两个数组的对比的方法

class Solution(object):
    def canConstruct(self, ransomNote, magazine):
        ransom_count = [0] * 26
        magazine_count = [0] * 26
        for c in ransomNote:
            ransom_count[ord(c) - ord('a')] += 1
        for c in magazine:
            magazine_count[ord(c) - ord('a')] += 1
        return all(ransom_count[i] <= magazine_count[i] for i in range(26))

15. 三数之和

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。

注意: 答案中不可以包含重复的三元组。

自己想

需要在一个数组里满足a+b+c = 0 相当于找a+b = -c,并且题目需要返回的是abc三个值的具体数值(所以前面第一题四数相加的思路或许不行)。思考起来反而有点像前两天看到的两数之和的题目“找出两个数使得和为target”,把target换成题目中的-c即可。

如果使用如四数相加的数据结构,字典,不太现实,因为字典不能通过value找到key,满足不了输出abc三个值的具体数值的要求。

那就使用排序后进行双指针的思路,先排序,双指针分别指头尾,大了移动尾指针,小了移动头指针。可是如何定义结束呢?那么可能还需要第三个指针指向数组中的“潜在的c”。

那么现在这个问题就转化为了,让数组中的所有数都成为一次“二数相加”问题中的tartget,然后移动头尾指针寻得答案。

由于tartget是在数组中被指出的,如果头或尾指针与tartget指针相遇,那么说明这个“潜在的target”是不可能存在的,于是移动target指针进行下一轮计算。

我的思路从未如此清晰,开写!

class Solution(object):
    def threeSum(self, nums):
        n = len(nums)
        nums = sorted(nums)
        result = []

        for target in  range(1,n-1):    #target的下标遍历范围为1~(n-2)
            left = 0
            right = n-1
            while left != target and right != target:
                if nums[left] + nums[right] + nums[target] > 0:
                    right -= 1
                elif nums[left] + nums[right] + nums[target] < 0:
                    left += 1
                else:
                    result.append([nums[left],nums[right],nums[target]])
                    right -=1
                    left +=1
        return result

好的,报错了,看了一下报错的用例是因为结果result中出现了重复的元组。

确实,遍历数组中的元素轮流当作target的话,如果元素有重复,那么结果元组也会有重复,所以在遍历target前先检查一下这个target是否与上一轮相同(因为nums已经被排序过);同理,如果left和right有重复的话也是,所以如果检查到指向的数与上一步相同,应该往下走一步

class Solution(object):
    def threeSum(self, nums):
        n = len(nums)
        nums = sorted(nums)
        result = []

        for target in  range(1,n-1):    #target的下标遍历范围为1~(n-2)
            left = 0
            right = n-1
            if nums[target] == nums[target-1]:
                left = target -1
            while left != target and right != target:
                if nums[left] + nums[right] + nums[target] > 0:
                    right -= 1
                elif nums[left] + nums[right] + nums[target] < 0:
                    left += 1
                else:
                    if left != 0 and nums[left] == nums[left-1]:
                        left +=1
                    elif right !=n-1 and nums[right] == nums[right+1]:
                        right -= 1
                    else:
                        result.append([nums[left],nums[right],nums[target]])
                        right -=1
                        left +=1
        return result

同样地,查看题解对比一下思路。

发现题解给出的思路跟我像的不太一样,我的思路是把数组中的每一个数当作一个target去遍历执行一次“二数之和”,直到遍历完成;题解给出的思路则是,每次都缩小寻找三数的范围,直到范围缩到最小,或者其中最小数大于0,

区别可以看图,很明显,题解(第二种)的解法要更好一些,因为第一种方法中,每一次都要对整个数组长度的内容进行搜索,而题解方法每次搜索的范围都在减少,也便于在最小的数大于0时对其进行剪枝

class Solution(object):
    def threeSum(self, nums):
        n = len(nums)
        nums = sorted(nums)
        result = []

        for i in  range(0,n):   
            if nums[i] > 0 :
                return result
            if i>0 and nums[i] == nums[i-1]:
                i += 1
                continue
            left = i + 1
            right = n-1
            while right > left:
                s = nums[i] + nums[left] + nums[right]
                if s < 0:
                    left += 1
                elif s > 0:
                    right -= 1
                else:
                    result.append([nums[i], nums[left], nums[right]])
                    
                    # 跳过相同的元素以避免重复
                    while right > left and nums[right] == nums[right - 1]:
                        right -= 1
                    while right > left and nums[left] == nums[left + 1]:
                        left += 1
                        
                    right -= 1
                    left += 1
                    
        return result

18. 四数之和

题意:给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。

a + b + c + d = target,相当于a + b + c =- (d+ target),也就是在三数之和的外面再嵌套一层对d的循环即可。

class Solution(object):
    def fourSum(self, nums, target):
        n = len(nums)
        nums = sorted(nums)
        result = []

        for i in range(n):
            if nums[i] > target and target > 0:
                break               #剪枝1
            if i>0 and nums[i]==nums[i-1]:
                continue        #去重1
            for j in range(i+1,n):
                if nums[i] + nums[j] > target and target > 0:
                    break          #剪枝2
                if j>(i+1) and nums[j]==nums[j-1]:
                    continue           #去重2
                left = j+1
                right = n-1
                while right > left:
                    s = nums[i]+nums[j]+nums[left]+nums[right]
                    if s >target:
                        right -= 1
                    elif s <target:
                        left += 1
                    else:
                        result.append([nums[i],nums[j],nums[left],nums[right]])
                        while right > left and nums[left]==nums[left+1]:
                            left += 1
                        while right > left and nums[right]==nums[right-1]:
                            right -= 1

                        left += 1
                        right -= 1
        return result

容易错误的是剪枝1和剪枝2的变化,在三数之和中这里剪枝的依据是target为0,如果最小的数都大于0了,那么再怎么加数字都计算不出结果了。但是在这里target可能是负数,如果最小的数(假设是-4)小于target(假设是-5),(-4)+(-1)仍然可以达到target,所以这个剪枝的前置条件是target要大于0

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值