越刷题越觉得自己进度慢、且要补的知识点越多了,所以加快下刷题进度吧。恰好接下来的 15 和 16 题都与三数之和相关,放到一起来记录下。
第 15 题 三数之和:
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例:
给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]
#来源:力扣(LeetCode)
#链接:https://leetcode-cn.com/problems/3sum
最初尝试了下遍历,穷举所有三元组,求和判断是否为 0,再记录不重复的结果,提交后一直超出时间限制。
联想到之前盛水容器那道题中的双指针法,可以基于判断有选择地避开不必要的穷举,于是在本题中应用双指针法来找和为 0 的三元组:遍历数组列表中的元素作为三元组的第一个,要求的三元组剩余两元素即双指针的值,双指针位于取值范围两端来缩小。
当得到和为 0 的三元组后,因为题目要求不能重复,所以要先检查下结果的列表中是否已经有该三元组:
# lst 为和为 0 的三元组
lst = [num_sort[i],num_sort[x],num_sort[y]]
# result 为结果列表,先判断是否包含该 lst,若没有再添加到结果中
if lst not in result:
result.append(lst)
但这么一路写下来,提交结果仍然是“超出时间限制”,猜测原因是对三元组是否已经存在的检查拖了后腿。最后,借鉴了一份题解代码,对重复三元组的处理从三元出发,当任一元出现重复时,直接忽略掉,最终总算顺利完成,我们结合着代码和注释来细看。
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
# 为了方便指针定位以及对三元处理,现将数组列表排序
num_sort = sorted(nums)
# 获取列表长度
l = len(num_sort)
# result 列表用来保存三元组结果
result=[]
# 三元组中的第一位用i来表示,它之后还有两元,故最大值 l-3
for i in range(l-2):
# x 和 y 是双指针,一个略大于 i,一个在最大边界 l-1
x,y = i+1,l-1
# 若整个列表最小值大于0,则全部都大于0;若最大值小于0,则全为负
if num_sort[i]>0 or num_sort[y]<0:
# 以上情况不会出现和为 0 的三元组,直接返回空列表
return result
# 这里用来对第一元去重,如果新拿到的 i 值和上一轮 i 值相同,直接跳过本次循环后续内容
if i>0 and num_sort[i]==num_sort[i-1]:
continue
# while 循环开启双指针的缩小范围
while x < y:
# 计算三元组的和
temp = num_sort[i] + num_sort[x] + num_sort[y]
# 如果和小于 0 ,为了得到和为 0 ,需要增加值,移动 x 指针
if temp<0:
x += 1
# 如果和大于 0,为了缩小到 0,需要减少值,移动 y 指针
elif temp>0:
y -= 1
# 如果和为0
else:
# 记录三元组
lst = [num_sort[i],num_sort[x],num_sort[y]]
# 结果中添加三元组
result.append(lst)
# 检测 x 之后的值是否与 x 相同,若是,通过 while 循环提高 x 的值到最后一个重复值
while x<y and num_sort[x+1]==num_sort[x]:
x += 1
# 检测 y 之前的值是否与 y 相同,若是,通过 while 循环缩减 y 的值到第一个重复值
while x<y and num_sort[y-1]==num_sort[y]:
y -=1
# 此时统一对 x 和 y 移动寻找下一组三元组
x += 1
y -= 1
# 整个 for 循环完成后,将记录结果的列表返回
return result
时间表现上出乎意料的好:
执行用时 : 656 ms, 在所有 Python3 提交中击败了 95.17% 的用户 内存消耗 : 16.4 MB, 在所有 Python3 提交中击败了 9.64%的用户
上述代码是一步步尝试出来的,最后提到的通过三元去重来规避三元组重复的思路也是借鉴的题解,所以等到完成后,代码也基本和其余题解一致了。前前后后修改、提交了10次,第10次才勉强通过验证。包括很多优化的想法与代码也基本在代码中实现到了。
第 16 题 最接近的三数之和:
给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。
例如,给定数组 nums = [-1,2,1,-4], 和 target = 1.
与 target 最接近的三个数的和为 2. (-1 + 2 + 1 = 2).
#来源:力扣(LeetCode)
#链接:https://leetcode-cn.com/problems/3sum-closest
如果顺利完成了第 15 题,对三数之和为特定值的情况熟悉了,到这个题目便可以省力许多。但我在这个题中还是采用比较保守,选用基于双指针优化过的穷举:仍是先将数组排序,遍历数组确定三个数的第一个,双指针代表剩余两个、分别位于范围两端。若判断三数之和小于 target,则向右移动较小的指针;若判断三数之和大于 target,则向左移动较大指针来缩小三数之和;若检测到三数之和为 target 则可提前结束直接返回 target 值了。
题目中只要求返回求和的值即可,但我仍是用字典保存了产生不同求和值情况下三个数的情况,具体我们来看代码。
class Solution:
def threeSumClosest(self, nums: List[int], target: int) -> int:
# 刚用 sorted,这里用 sort 来进行排序
nums.sort()
l = len(nums)
# dic 字典储存求和值对应的三数情况
dic = {}
# 三数中第一个用 i 来定位
for i in range(l-2):
# 双指针 x 和 y,左侧指针略大于 i,右侧指针在右侧边界
x,y = i+1,l-1
# while 循环来缩小双指针范围
while x<y:
# 三数求和
temp = nums[i]+nums[x]+nums[y]
# 将求和值和对应三个数组成的列表存储到字典中
dic[temp] = [nums[i],nums[x],nums[y]]
# 若和小于 target,为了更靠近,右移左侧指针增加和
if temp < target:
x+=1
# 若和大于 target,移动右侧指针
elif temp >target:
y-=1
# 若和为 target,提前结束返回
else:
return target
# 若执行完整个 for 循环,我们得到了双指针法求和结果的字典
# 获取字典的 keys() 即求和值的列表,先排序
target_key = sorted(list(dic.keys()))
# 计算排序后第一位与 target 差值的绝对值
target_min = abs(target - target_key[0])
# result 用来记录返回结果
result = 0
# 对排序后的求和值列表进行遍历
for i,n in enumerate(target_key):
# 对每个求和值进行运算,求它们与 target 差值的绝对值
tmp = abs(n-target)
# 找最小的差值,即最接近 target
target_min = min(target_min,tmp)
# 如果此位置恰好满足更小的情况,将此时求和值赋值给 result
if target_min == tmp:
result = n
# 经过遍历,把差值最小时的求和值已经赋值给 result了,最终返回
return result
时间表现上依旧不错,应该是双指针法立功了:
执行用时 : 120 ms, 在所有 Python3 提交中击败了 71.18% 的用户 内存消耗 : 13.7 MB, 在所有 Python3 提交中击败了 9.38% 的用户
回头看代码,感觉双指针法只是精简了遍历过程,我将所有的求和情况都记录在了字典中,最后再独立地对字典中的求和值进行运算找到与 target 最接近的值,这一步如果能优化下、通过双指针过程直接实现应该不错。
参考其它题解代码,确实如此,无需再单独对所有求和值进行新一轮比较,在求完和后直接比较保存即可,且题目只要求和值即可,无需我们定义的字典。
class Solution:
def threeSumClosest(self, nums: List[int], target: int) -> int:
nums.sort()
l = len(nums)
# result 默认为无穷大
result = float("inf")
# 双指针法来表示三个数
for i in range(l-2):
x,y = i+1,l-1
# 对 x 和 y 双指针进行处理
while x<y:
# temp 为此时三数的和
temp = nums[i]+nums[x]+nums[y]
if temp < target:
x+=1
elif temp >target:
y-=1
else:
return target
# 如果此时和与 target 差值更小,则保存此求和到 result 中
if abs(temp - target) < abs(result-target):
result = temp
return result
代码更精炼了,但貌似表现没有提高多少:
执行用时 : 136 ms, 在所有 Python3 提交中击败了 56.67% 的用户 内存消耗 : 13.6 MB, 在所有 Python3 提交中击败了 9.38% 的用户
猜测可能是这个计算差值绝对值的比较过程比较费时吧。
第 15 和 16 题,两道均为中等难度题,解题思路都是基于双指针法进行精简过的穷举求和判断。也感谢这两道题,我对双指针法的理解又加深了些。
同时,最后一段优化代码中,接触到了 Python 中的无穷大 float("inf"),当然也顺手查到了无穷小 float("-inf"),之后如果有类似的比较最大值最小值,可以为参与比较的变量设定这么个初始值。
收获还行,明天继续~