看到有大佬总结了一些相关题目,想着先刷一类。
- 1.两数之和
- 15.三数之和
- 16.最接近的三数之和
- 11.盛最多的水
- 18.四数之和
- 454.四数相加II
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
示例 2:
输入:nums = [3,2,4], target = 6
输出:[1,2]
示例 3:
输入:nums = [3,3], target = 6
输出:[0,1]
提示:
-2 <= nums.length <= 10^4
-10^9<= nums[i] <= 10^9
-10^9 <= target <= 10^9
只会存在一个有效答案
看了大神的一些解体参考,暴力破解法双层for循环查找的话,时间复杂度是O(n^2),会出现RuntimeOver。
方法一:用python的list相关函数求解该题的解决方案是找到 num2 = target - num1是否在该list中,如果num2在list中查找出其下标,并返回。
- num2 in nums,返回 True
- nums.index(num2),查找 num2 的索引
这样需要对于每一个num1,对整个nums列表做一次啊查询,为了提高时间复杂度,num2 的查找并不需要每次从 nums 查找一遍,只需要从 num1 位置之前或之后查找即可。这里选择从num1位置之前的元素中找。
def twoSum(nums, target):
lens = len(nums)
j=-1
for i in range(1,lens):
temp = nums[:i]
if (target - nums[i]) in temp:
j = temp.index(target - nums[i])
break
if j>=0:
return [j,i]
方法二:直接使用字典求解
用字典的方法进行求解,使用enumerate枚举所有元素,省去了查找索引,效率很快
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
records = dict() #创建空字典
# 用枚举更方便,就不需要通过索引再去取当前位置的值
for idx, val in enumerate(nums):
if target - val not in records:
records[val] = idx
else:
return [records[target - val], idx] # 如果存在就返回字典记录索引和当前索引
方法三:用字典模拟哈希求解
参考了大神们的解法,通过哈希来求解,这里通过字典来模拟哈希查询的过程。其实就是字典记录了 num1 和 num2 的值和位置,不需要再去查询一遍索引的步骤。
def twoSum(nums, target):
hashmap={}
for ind,num in enumerate(nums):
hashmap[num] = ind
for i,num in enumerate(nums):
j = hashmap.get(target - num)
if j is not None and i!=j:
return [i,j]
小话痨:开始使用python刷题啦(最近在学机器学习),用leetcode边刷题,边练python,顺便写点小总结。
这题,让我记住了python中list(列表)类型、dictonary(字典)类型、内置函数enumerate。
enumerate(sequence, [start=0])15. 三数之和
给你一个包含 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]]
虽然题目意思和两数之和类似,但看了官方题解和做法后,发现用的方法不尽相同。
任意一个三元组的和都为 0。如果直接使用三重循环枚举三元组,会得到 O(N3)个满足题目要求的三元组(其中 NN 是数组的长度)时间复杂度至少为 O(N^3)。在这之后,我们还需要使用哈希表进行去重操作,得到不包含重复三元组的最终答案,又消耗了大量的空间。这个做法的时间复杂度和空间复杂度都很高,因此我们要换一种思路来考虑这个问题。
该提要求不重复的三元组,不重复,也就是说,我们枚举的三元组 (a, b, c)需要满足a ≤ b ≤ c,保证了只有 (a, b, c) 这个顺序会被枚举到。要实现这一点,我们可以将数组中的元素从小到大进行排序,随后使用普通的三重循环就可以满足上面的要求。
同时,对于每一重循环而言,相邻两次枚举的元素不能相同,否则也会造成重复。举个例子,如果排完序的数组为 [0, 1, 2, 2, 2, 3] ,我们使用三重循环枚举到的第一个三元组为 (0, 1, 2),如果第三重循环继续枚举下一个元素,那么仍然是三元组 (0, 1, 2),产生了重复。因此我们需要将第三重循环跳到下一个不相同的元素,即数组中的最后一个元素 3,枚举三元组 (0, 1, 3)。
这种方法的时间复杂度仍然为 O(N^3),毕竟我们还是没有跳出三重循环的大框架。然而它是很容易继续优化的,可以发现,如果我们固定了前两重循环枚举到的元素 a 和 b,那么只有唯一的 c 满足 a+b+c=0。当第二重循环往后枚举一个元素 b’ 时,由于 b’ > b,那么满足 a+b’+c’=0 的 c’ 一定有 c’ < c,即 c’ 在数组中一定出现在 c 的左侧。也就是说,我们可以从小到大枚举 b,同时从大到小枚举 c,即第二重循环和第三重循环实际上是并列的关系。
有了这样的发现,我们就可以保持第二重循环不变,而将第三重循环变成一个从数组最右端开始向左移动的指针。并且,保持左指针一直在右指针的左侧(即满足 b≤c)。
这个方法就是我们常说的双指针,当我们需要枚举数组中的两个元素时,如果我们发现随着第一个元素的递增,第二个元素是递减的,那么就可以使用双指针的方法。
(看了官方题解和大佬的题解,我认为同一种方法大佬的代码更加简单易理解)
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
n=len(nums)
if(not nums or n<3):
return []
nums.sort()
res=[]
for i in range(n):
if(nums[i]>0):
# 如果第一个数就大于0,排序后的两个数不可能使得三个数=0
return res
if(i>0 and nums[i]==nums[i-1]):
continue
L=i+1
R=n-1
while(L0):
R=R-1
else:
L=L+1
return res
复杂度分析
- 时间复杂度:O(N^2),其中 N 是数组 nums 的长度。数组排序O(NlogN)
- 空间复杂度:O(logN)。我们忽略存储答案的空间,额外的排序的空间复杂度为 O(logN)。然而我们修改了输入的数组 nums,在实际情况下不一定允许,因此也可以看成使用了一个额外的数组存储了nums 的副本并进行排序,空间复杂度为 O(N)。
给你一个长度为 n 的整数数组 nums 和 一个目标值 target。请你从 nums 中选出三个整数,使它们的和与 target 最接近。
返回这三个数的和。
假定每组输入只存在恰好一个解。
这道题虽与上一道相似,但实际上因为a+b+c=0有不少特殊性质,并不能完全借鉴思路。
这道题的题目比较短,获取最接近target目标值的三数之和。
首先拿到这种题,我们要先看下提示中的取值范围,3 <= nums.length <= 103条件标志着。不会出现不足三个数字的异常场景,但103 如果三层for循环109必然会超时,暴力解法不通。
那么,下来就需要考虑优化方案:
- 二分查找 or hash表?
二分查找和hash表只针对单个数字,那势必我们需要先双层循环再二分,10^6一样会超时。 - 缩减条件
既然三个数字我们有些无从下手,那么先使用一层for循环,减少一个数字的筛选再来考虑是否就简单了一些。
减少一个数字后,我们的题目变成了查找数组中某两个最接近target - num1,就变成了一道两数之和的基础题。 - 实现方案
我们先将nums排序;
设置返回值res,初始为无穷大;
循环开始:从第一个数nums[i]后的数开始,设置left = i + 1, right = length - 1;tmp = nums[left] + nums[rigth]
比较 res和tmp+nums[i],哪个更接近target,并赋值给res
如果tmp = target - nums[i],表示找到了三个数等于target直接返回target
如果tmp > target - nums[i],我们将right向左移一个
如果tmp < target - nums[i],我们将left向右移一个
最终,即可获取结果。
排序+双指针
同时,借鉴上一道题,可以直接将其移动到下一个与这次枚举到的不相同的元素,减少枚举的次数,缩短时间。(但这样会消耗内存)
class Solution:
def threeSumClosest(self, nums: List[int], target: int) -> int:
ret = float('inf')
nums.sort()
length = len(nums)
for i in range(length - 2):
left = i + 1
right = length - 1
while left < right:
tmp = nums[i] + nums[left] + nums[right]
ret = tmp if abs(tmp - target) < abs(ret - target) else ret #取更近的
if tmp == target:
return target
if tmp > target:
r = right - 1
# 移动到下一个不相等的元素
while left < r and nums[r] == nums[right]:
r -= 1
right = r
# right- =1
else:
# 如果和小于 target,移动 left 对应的指针
l = left + 1
# 移动到下一个不相等的元素
while l < right and nums[l] == nums[left]:
l += 1
left = l
#left- =1
return ret
11. 盛最多水的容器
给你 n 个非负整数 a1,a2,…,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
说明:你不能倾斜容器。
示例:
输入:[1,8,6,2,5,4,8,3,7]
输出:49
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
这一题使用:双指针。
容纳的水量=两个指针指向的数字中较小值*指针之间的距离
至于怎么移动前后两个指针?
如果我们移动数字较大的那个指针,那么前者「两个指针指向的数字中较小值」不会增加,后者「指针之间的距离」会减小,那么这个乘积会减小。因此,我们移动数字较大的那个指针是不合理的。因此,我们移动数字较小的那个指针。
以示例为例:在初始时[1, 8, 6, 2, 5, 4, 8, 3, 7],左右指针分别指向数组的左右两端,它们可以容纳的水量为 min(1, 7) * 8 =8。我们将左指针向右移动,此时可以容纳的水量为 min(8, 7) * 7 =49。
class Solution:
def maxArea(self, height: List[int]) -> int:
l, r = 0, len(height) - 1
ans = 0
while l < r:
area = min(height[l], height[r]) * (r - l)
ans = max(ans, area)
if height[l] <= height[r]:
l += 1
else:
r -= 1
return ans
18. 四数之和
给你一个由 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
你可以按 任意顺序 返回答案 。
本题与「15. 三数之和」相似,解法也相似。排序+双指针
为了避免枚举到重复四元组,则需要保证每一重循环枚举到的元素不小于其上一重循环枚举到的元素,且在同一重循环中不能多次枚举到相同的元素。
为了实现上述要求,可以对数组进行排序,并且在循环过程中遵循以下两点:
- 每一种循环枚举到的下标必须大于上一重循环枚举到的下标;
- 同一重循环中,如果当前元素与上一个元素相同,则跳过当前元素。
使用上述方法,可以避免枚举到重复四元组,但是由于仍使用四重循环,时间复杂度仍是 O(n^4)。注意到数组已经被排序,因此可以使用双指针的方法去掉一重循环。
使用两重循环分别枚举前两个数,然后在两重循环枚举到的数之后使用双指针枚举剩下的两个数。假设两重循环枚举到的前两个数分别位于下标 i 和 j,其中 i 具体实现时,还可以进行一些剪枝操作: 复杂度分析 看了大佬的解法,不用指针不用指针!!回溯解法 剪枝很重要!!! 但该方法时间复杂度比方法一高高 给你四个整数数组 nums1、nums2、nums3 和 nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足: 示例: 和18.四数之和不同的是,这题题是从四个数组中各取一个数。 我们可以将四个数组分成两部分,A 和 B 为一组,C 和 D 为一组。 对于 A 和 B,我们使用二重循环对它们进行遍历,得到所有 A[i]+B[j] 的值并存入哈希映射中。对于哈希映射中的每个键值对,每个键表示一种 A[i]+B[j],对应的值为 A[i]+B[j] 出现的次数。 对于 C 和 D,我们同样使用二重循环对它们进行遍历。当遍历到 C[k]+D[l] 时,如果 −(C[k]+D[l]) 出现在哈希映射中,那么将 −(C[k]+D[l]) 对应的值累加进答案中。 最终即可得到满足 A[i]+B[j]+C[k]+D[l]=0 的四元组数目。 复杂度分析 时间复杂度:O(n^2)。 空间复杂度:O(n^2),即为哈希映射需要使用的空间。 这个模块实现了特定目标的容器,以提供Python标准内建容器 dict、list、set、tuple 的替代选择。class Solution:
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
quadruplets = list()
if not nums or len(nums) < 4:
return quadruplets
nums.sort()
length = len(nums)
for i in range(length - 3):
if i > 0 and nums[i] == nums[i - 1]:
continue
if nums[i] + nums[i + 1] + nums[i + 2] + nums[i + 3] > target:
break
if nums[i] + nums[length - 3] + nums[length - 2] + nums[length - 1] < target:
continue
for j in range(i + 1, length - 2):
if j > i + 1 and nums[j] == nums[j - 1]:
continue
if nums[i] + nums[j] + nums[j + 1] + nums[j + 2] > target:
break
if nums[i] + nums[j] + nums[length - 2] + nums[length - 1] < target:
continue
left, right = j + 1, length - 1
while left < right:
total = nums[i] + nums[j] + nums[left] + nums[right]
if total == target:
quadruplets.append([nums[i], nums[j], nums[left], nums[right]])
while left < right and nums[left] == nums[left + 1]:
left += 1
left += 1
while left < right and nums[right] == nums[right - 1]:
right -= 1
right -= 1
elif total < target:
left += 1
else:
right -= 1
return quadruplets
方法二
排序的时间复杂度是O(nlogn),枚举四元组的时间复杂度是 O(n^3)
空间复杂度主要取决于排序额外使用的空间。此外排序修改了输入数组 nums,实际情况中不一定允许,因此也可以看成使用了一个额外的数组存储了数组 nums 的副本并排序,空间复杂度为O(n)。
思路:
1.对所有数字来说,都有选择,和不选择两种情况。
2. 选择一个数,target就减去这个数,最后target == 0,并且只选择了4个数就结束。
3. 不选择这个数,就去检查下一个数
如果排序了数组nums之后。
target - nums[i] - (3 - len(oneSolution)) * nums[-1] > 0
target - (4 - len(oneSolution)) * nums[i] < 0class Solution:
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
nums.sort()
output = []
def Search(i, target, oneSolution, notSelected):
if target == 0 and len(oneSolution) == 4:
output.append(oneSolution)
return
elif len(oneSolution) > 4 or i >= len(nums):
return
if target - nums[i] - (3 - len(oneSolution)) * nums[-1] > 0 or nums[i] in notSelected:
Search(i + 1, target, oneSolution, notSelected)
elif target - (4 - len(oneSolution)) * nums[i] < 0:
return
else:
Search(i + 1, target, oneSolution, notSelected + [nums[i]])
Search(i + 1, target - nums[i], oneSolution + [nums[i]], notSelected)
Search(0, target, [], [])
return output
454.四数相加II
输入:nums1 = [1,2], nums2 = [-2,-1], nums3 = [-1,2], nums4 = [0,2]
输出:2
解释:
两个元组如下:
(0, 0, 0, 1) -> nums1[0] + nums2[0] + nums3[0] + nums4[1] = 1 + (-2) + (-1) + 2 = 0
(1, 1, 0, 0) -> nums1[1] + nums2[1] + nums3[0] + nums4[0] = 2 + (-1) + (-1) + 0 = 0
且要求的输出是一共有多少个元组class Solution:
def fourSumCount(self, A: List[int], B: List[int], C: List[int], D: List[int]) -> int:
dic = collections.Counter(u + v for u in A for v in B)
return sum(dic.get(- c - d, 0) for c in C for d in D)
关于Python中collections模块
我们使用了两次二重循环,时间复杂度均为 O(n^2)。在循环中对哈希映射进行的修改以及查询操作的期望时间复杂度均为 O(1)。
在最坏的情况下,A[i]+B[j] 的值均不相同,因此值的个数为 n^2,也就需要 O(n^2)的空间



