两数,三数之和

1. 两数之和

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

示例 1:
输入:nums = [2,3,5,7,11,13], target = 12
输出:[2,3]
解释:因为 nums[2] + nums[3] == 12 ,返回 [2, 3] 。

1. 两个循环

最直接的解法就是数组的数两两组合,然后求出和即可,这个直接使用两个循环即可,因为是两两组合,并且不需要重复,所以j直接从i后面开始选取即可

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        n = len(nums)

        for i in range(n):
            for j in range(i+1,n):
                if nums[i] + nums[j] == target:
                    return i,j

2. 排序+双指针

如果不考虑位置,直接返回两个数据,我们还有一种双指针的方法。双指针的思想也非常的简单,我们首先对数组进行升序排序,然后两个指针分别指向头和尾

  1. if nums[L]+nums[R]>tartet : R-=1
  2. if nums[L]+nums[R]<tartet : L+=1
23571113
LR

2+13>12,此时两个数的和大于目标值,我们移动右指针,这样可以让两个数的和小一点

23571113
LR

2+11>12,此时两个数的和大于目标值,我们移动右指针,这样可以让两个数的和小一点

23571113
LR

2+7<12,此时两个数的和小于目标值,我们移动左指针,这样可以让两个数的和大一点

23571113
LR

3+7<12,此时两个数的和小于目标值,我们移动左指针,这样可以让两个数的和大一点

23571113
LR

5+7=12,找到了一组数据。
可以看到,双指针的第一步就是对数组进行排序,这会直接打乱原来数组的顺序,因此即使得到了满足targe的两个数据,也需要通过index函数映射回去

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        arr = zip(nums,range(len(nums)))
        arr = sorted(arr, key=lambda x:x[0])
        l,r=0,len(nums)-1
        while l < r:
            print(l,r)
            if arr[l][0]+arr[r][0]>target:r-=1
            elif arr[l][0]+arr[r][0]<target:l+=1
            else: return arr[l][1],arr[r][1]

3. hash

然后我看了另一种解法,也是非常的巧妙,使用hash表来存储数据。对于数组中的某个数nums[i],我们需要找到一个数满足target-nums[i],即在原始的数组中查找是否存在一个数nums[j]=target-nums[i],这就转变成了一个查找问题。

nums[i]27115
nums[j]=target-nums[i]9-2=79-7=29-11=-29-5=4

三数之和

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。

示例 1:
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]

1. 三层循环(暴力算法无所畏惧)

最直接的想法就是三层for循环来求解三个数组的组合。不过调试的过程中发现数组中会出现相同的元素,这就回导致出现重复的结果,例如示例1,由于重复出现了-1,因此在循环的过程中也出现了重复,即第一个-1和第二个-1都可以与0,1组合后满足结果,因此出现了两次。

[[-1,0,1],[-1,2,-1],[0,1,-1]]

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        res = []
        n = len(nums)
        for i in range(n):
            for j in range(i+1,n):
                for k in range(j+1,n):
                    if nums[i] + nums[j] + nums[k] == 0:
                        res.append([nums[i],nums[j],nums[k]])
        return res

我们可以通过对数组排序来处理重复问题,可以看到有两个-1,我分别用红色和绿色标记出来。

  1. 第一个-1会与0,1,2进行组合,第二个-1也会与0,1,2进行组合,这就会导致重复的结果,因此我们只需要选择一个-1就可以了
  2. 应该选择哪一个-1呢?很明显应该选择第一个-1,因为第一个-1还可以与第二个-1进行组合,第一个-1的组合数已经包含了第二个-1的组合数
    在这里插入图片描述
class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        res = []
        nums.sort()
        n = len(nums)
        for i in range(n):
            if i > 0 and nums[i] == nums[i-1]:continue # 保留第一个
            for j in range(i+1,n):
                for k in range(j+1,n):
                    if nums[i] + nums[j] + nums[k] == 0:
                        res.append([nums[i],nums[j],nums[k]])
        return res

这样依然会有重复的问题,举个例子nums=[-5,0,2,2,3,3],会出现两次2,3的结果,因此内部的循环依然需要做判断,但是通过循环很难去重了。可以使用双指针来去重

2.排序+双指针

数组已经排好了顺序,我们之前求解两数之和的时候,也使用了双指针的方法。但是现在需要考虑去重的问题。确定第一个元素之后,我们使用两个指针分别指向头和尾

  1. if nums[F] + nums[L]+nums[R]>0 : R-=1
  2. if nums[F] + nums[L]+nums[R]<0 : L+=1
-6-5122334
FirstLR
FirstLR
FirstLR

现在我们已经求出了一组解了,但是题目要求的是让我们把所有的可行解都求解出来,所以需要继续移动双指针。但是这个case可以发现如果我们移动做指针得到的依然是重复解,因此需要去重,即

while L < R and nums[L]==nums[L+1]:L+=1
while L < R and nums[R]==nums[R-1]:R-=1

核心点:通过循环一直到找到下一个不同的数字即可

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        res = []
        nums.sort()
        n = len(nums)
        for i in range(n):
            if i > 0 and nums[i] == nums[i-1]:continue
            l,r=i+1,n-1
            while l < r:
                if nums[l]+nums[r]>-nums[i]:
                    r -= 1
                elif nums[l]+nums[r]<-nums[i]:
                    l += 1
                else:
                    res.append([nums[l],nums[r],nums[i]])
                    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

循环变搜索

明明是两层循环为什么改成双指针之后就可以只需要循环一次了呢?这是因为排完序之后,双指针可以过滤很多不需要循环的操作。
举个具体的case,例如求两数之和,其中nums = [2,3,5,7,11,13], target = 12,

for i in range(n):
	for j in range(i+1,n):
		print(nums[i],nums[j])
		if nums[i]+nums[j] == target:
			res.append(nums[i],nums[j])

如果我们先确定一个数2,然后跟后面的2,3,5,7,11,13进行组合,当我们组合到11的时候2+11>13,那么我们还需要去跟更大的13去组合吗,显然是不需要的,所以我们可以直接加一个判断,跳过后面的数字

for i in range(n):
	for j in range(i+1,n):
		print(nums[i],nums[j])
		if nums[i]+nums[j] == target:
			res.append(nums[i],nums[j])
		elif nums[i]+nums[j] > target:
			break

现在就回发现一个有趣的点了,2和11组合大于12,因此2不需要再跟13组合,那么第一层循环是3,还需要跟11和13组合吗,肯定也不需要了,也就是说,我们第二层循环的结尾不一定始终是n,而是可以不断往前推的

r = n
for i in range(n):
	for j in range(i+1,r):
		print(nums[i],nums[j])
		if nums[i]+nums[j] == target:
			res.append(nums[i],nums[j])
		elif nums[i]+nums[j] > target:
			r = j
			break
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值