两数,三数之和
Posted 我家大宝最可爱
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了两数,三数之和相关的知识,希望对你有一定的参考价值。
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. 排序+双指针
如果不考虑位置,直接返回两个数据,我们还有一种双指针的方法。双指针的思想也非常的简单,我们首先对数组进行升序排序,然后两个指针分别指向头和尾
if nums[L]+nums[R]>tartet : R-=1
if nums[L]+nums[R]<tartet : L+=1
2 | 3 | 5 | 7 | 11 | 13 |
---|---|---|---|---|---|
L | R |
2+13>12,此时两个数的和大于目标值,我们移动右指针,这样可以让两个数的和小一点
2 | 3 | 5 | 7 | 11 | 13 |
---|---|---|---|---|---|
L | R |
2+11>12,此时两个数的和大于目标值,我们移动右指针,这样可以让两个数的和小一点
2 | 3 | 5 | 7 | 11 | 13 |
---|---|---|---|---|---|
L | R |
2+7<12,此时两个数的和小于目标值,我们移动左指针,这样可以让两个数的和大一点
2 | 3 | 5 | 7 | 11 | 13 |
---|---|---|---|---|---|
L | R |
3+7<12,此时两个数的和小于目标值,我们移动左指针,这样可以让两个数的和大一点
2 | 3 | 5 | 7 | 11 | 13 |
---|---|---|---|---|---|
L | R |
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] | 2 | 7 | 11 | 5 |
---|---|---|---|---|
nums[j]=target-nums[i] | 9-2=7 | 9-7=2 | 9-11=-2 | 9-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会与0,1,2进行组合,第二个-1也会与0,1,2进行组合,这就会导致重复的结果,因此我们只需要选择一个-1就可以了
- 应该选择哪一个-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.排序+双指针
数组已经排好了顺序,我们之前求解两数之和的时候,也使用了双指针的方法。但是现在需要考虑去重的问题。确定第一个元素之后,我们使用两个指针分别指向头和尾
if nums[F] + nums[L]+nums[R]>0 : R-=1
if nums[F] + nums[L]+nums[R]<0 : L+=1
-6 | -5 | 1 | 2 | 2 | 3 | 3 | 4 |
---|---|---|---|---|---|---|---|
First | L | R | |||||
First | L | R | |||||
First | L | R |
现在我们已经求出了一组解了,但是题目要求的是让我们把所有的可行解都求解出来,所以需要继续移动双指针。但是这个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
以上是关于两数,三数之和的主要内容,如果未能解决你的问题,请参考以下文章