1、有点像“leetcode分类刷题:二分查找(Binary Search)(一、基于索引(定义域)的类型)”的前提,数组是排序好的,然后求解的目标是元素之和,不是查找某个元素第一次大于等于数组元素的索引位置
2、稍微复杂化的题型是对三元组、四元组等的求解,就像“leetcode分类刷题:哈希表(Hash Table)(一、简单的两数之和)”中所提到的,这类数组的元素求和题目,难点在于求解的目标,这里需要返回答案的值构成的三元组、四元组等,就需要考虑答案的去重问题;额外可以考虑加上 特殊情况的判断(当然不加这个的代码也能顺利通过),需要注意target值为负数还是非负数进行分类讨论,最后总结这类题目的解题思路:排序+双指针+答案的去重+特殊情况的判断(可以不加)
1、本文总结的经典题型,注意细节:下标从1开始
2、该题为后续几道题的基础题
from typing import List
'''
167. 两数之和 II - 输入有序数组
给你一个下标从 1 开始的整数数组numbers ,该数组已按 非递减顺序排列 ,请你从数组中找出满足相加之和等于目标数target 的两个数。
如果设这两个数分别是 numbers[index1] 和 numbers[index2] ,则 1 <= index1 < index2 <= numbers.length 。
以长度为 2 的整数数组 [index1, index2] 的形式返回这两个整数的下标 index1 和 index2。
你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。
你所设计的解决方案必须只使用常量级的额外空间。
示例 1:
输入:numbers = [2,7,11,15], target = 9
输出:[1,2]
解释:2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。返回 [1, 2] 。
题眼:两数之和+有序
思路:有序问题使用双指针最好
'''
class Solution:
def twoSum(self, numbers: List[int], target: int) -> List[int]:
left, right = 0, len(numbers) - 1
while left < right: # 答案的下标不能重复
if numbers[left] + numbers[right] == target:
return [left+1, right+1] # 下标从1开始
elif numbers[left] + numbers[right] > target:
right -= 1
else:
left += 1
if __name__ == "__main__":
obj = Solution()
while True:
try:
in_line = input().strip().split('=')
nums = [int(n) for n in in_line[1].split(']')[0].split('[')[1].split(',')]
target = int(in_line[2].strip())
print(obj.twoSum(nums, target))
except EOFError:
break
思路:不同于返回下标,这里是返回值;"167. 两数之和 II - 输入有序数组"的扩展 + 对三元组的答案在每个位置都要添加去重逻辑;
额外可以考虑加上 特殊情况的判断(需要注意target值为负数还是非负数)
from typing import List
'''
15. 三数之和
给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。
请返回所有和为 0 且不重复的三元组。注意:答案中不可以包含重复的三元组。
示例 1:
输入: nums = [-1,0,1,2,-1,-4]
输出: [[-1,-1,2],[-1,0,1]]
解释: nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。 注意,输出的顺序和三元组的顺序并不重要。
题眼:这种题目的复杂程度取决于求解目标:求值构成的三元组 且不能重复
思路:不同于返回下标,这里是返回值;"167. 两数之和 II - 输入有序数组"的扩展 + 对三元组的答案在每个位置都要添加去重逻辑;
额外可以考虑加上 特殊情况的判断(需要注意target值为负数还是非负数)
'''
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
# 这种题目的复杂程度取决于求解目标:求值构成的三元组 且不能重复
result = []
nums.sort()
for i in range(len(nums) - 2):
# 特殊情况判断:三元组答案的第一个元素大于target,不必再求解后续位置元素
if nums[i] > 0:
break
# 三元组答案的第一个元素去重
if i > 0 and nums[i] == nums[i - 1]:
continue
# "167. 两数之和 II - 输入有序数组"的扩展,查找剩余二元组,target值为-nums[i];但要注意剩余二元组的去重
left, right = i + 1, len(nums) - 1
while left < right:
# 双指针遍历过程
if nums[left] + nums[right] > -nums[i]:
right -= 1
elif nums[left] + nums[right] < -nums[i]:
left += 1
else:
result.append([nums[i], nums[left], nums[right]])
left += 1
right -= 1
# 三元组答案的第二、三个元素去重
while left < right and nums[left] == nums[left - 1]:
left += 1
while left < right and nums[right] == nums[right + 1]:
right -= 1
return result
if __name__ == "__main__":
obj = Solution()
while True:
try:
nums = [int(n) for n in input().strip().split('[')[1][:-1].split(',')]
print(nums)
print(obj.threeSum(nums))
except EOFError:
break
思路:“15. 三数之和”的扩展 + 对三元组的答案在每个位置都要添加去重逻辑;
额外可以考虑加上 特殊情况的判断(需要注意target值为负数还是非负数)
from typing import List
'''
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
示例 1:
输入: nums = [1,0,-1,0,-2,2], target = 0
输出: [[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]
题眼:这种题目的复杂程度取决于求解目标:求值构成的四元组 且不能重复
思路:“15. 三数之和”的扩展 + 对三元组的答案在每个位置都要添加去重逻辑;
额外可以考虑加上 特殊情况的判断(需要注意target值为负数还是非负数)
'''
class Solution:
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
result = []
# 情况1、数组长度小于4,直接返回[]
if len(nums) < 4:
return result
nums.sort()
for i in range(len(nums) - 3):
# 特殊情况判断:四元组答案的第一个元素情况(需要注意target值为负数还是非负数)
if target >= 0 and nums[i] > target or target < 0 and nums[i] >= 0:
break
# 四元组答案的第一个元素去重
if i > 0 and nums[i] == nums[i - 1]:
continue
# 寻找剩余三元组的过程
for j in range(i + 1, len(nums) - 2):
# 特殊情况判断:四元组答案的第一个元素情况(需要注意target值为负数还是非负数)
if target >= 0 and nums[i] + nums[j] > target or target < 0 and nums[i] + nums[j] >= 0:
break
# 四元组答案的第二个元素去重
if j > i + 1 and nums[j] == nums[j - 1]:
continue
# 寻找剩余二元组的过程
left, right = j + 1, len(nums) - 1
while left < right:
# 双指针遍历过程
if nums[left] + nums[right] < target - nums[i] - nums[j]:
left += 1
elif nums[left] + nums[right] > target - nums[i] - nums[j]:
right -= 1
else:
result.append([nums[i], nums[j], nums[left], nums[right]])
left += 1
right -= 1
# 四元组答案的第三、四个元素去重
while left < right and nums[left] == nums[left - 1]:
left += 1
while left < right and nums[right] == nums[right + 1]:
right -= 1
return result
if __name__ == "__main__":
obj = Solution()
while True:
try:
in_line = input().strip().split('=')
nums = [int(n) for n in in_line[1].split(']')[0].split('[')[1].split(',')]
target = int(in_line[2].strip())
print(nums, target)
print(obj.fourSum(nums, target))
except EOFError:
break
思路:"167. 两数之和 II - 输入有序数组"的扩展,好像变得更简单了,因为不需要考虑答案去重了
from typing import List
'''
16. 最接近的三数之和
给你一个长度为 n 的整数数组nums和 一个目标值target。请你从 nums 中选出三个整数,使它们的和与target最接近。
返回这三个数的和。
假定每组输入只存在恰好一个解。
示例 1:
输入:nums = [-1,2,1,-4], target = 1
输出:2
解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。
题眼:三数之和
思路:"167. 两数之和 II - 输入有序数组"的扩展
'''
class Solution:
def threeSumClosest(self, nums: List[int], target: int) -> int:
nums.sort()
result = nums[0] + nums[1] + nums[2] # 将result初始值设定为排序后的前三个元素之和 或者 设置为float('inf)
for i in range(len(nums) - 2):
# "167. 两数之和 II - 输入有序数组"的扩展
left, right = i + 1, len(nums) - 1
while left < right:
s = nums[i] + nums[left] + nums[right]
if abs(s - target) < abs(result - target):
result = s
if s == target:
return result
elif s > target: # 更接近的加和 需要变小,更新右指针
right -= 1
else: # 更接近的加和 需要变大,更新左指针
left += 1
return result
if __name__ == "__main__":
while True:
try:
in_line = input().strip().split('=')
nums = [int(n) for n in in_line[1].split('[')[1].split(']')[0].split(',')]
target = int(in_line[2])
print(nums, target)
except EOFError:
break
思路:"16. 最接近的三数之和"的扩展,求解目标变成了三元组的个数,也不需要考虑去重的问题
from typing import List
'''
259. 较小的三数之和
给定一个长度为 n 的整数数组和一个目标值 target,寻找能够使条件 nums[i] + nums[j] + nums[k] < target
成立的三元组 i, j, k 个数(0 <= i < j < k < n)。
示例 1:
输入: nums = [-2,0,1,3], target = 2
输出: 2
解释: 因为一共有两个三元组满足累加和小于 2:
[-2,0,1]
[-2,0,3]
题眼:三数之和
思路:"16. 最接近的三数之和"的扩展,求解目标变成了三元组的个数,不考虑去重的问题
'''
class Solution:
def threeSumSmaller(self, nums: List[int], target: int) -> int:
nums.sort()
result = 0
for i in range(len(nums) - 2):
# "167. 两数之和 II - 输入有序数组"的扩展
left, right = i + 1, len(nums) - 1
while left < right:
s = nums[i] + nums[left] + nums[right]
if s < target:
result += right - left # (left, right]区间的所有数值全部满足
left += 1
else:
right -= 1
return result
if __name__ == "__main__":
obj = Solution()
while True:
try:
in_line = input().strip().split('=')
nums = [int(n) for n in in_line[1].split('[')[1].split(']')[0].split(',')]
target = int(in_line[2])
print(obj.threeSumSmaller(nums, target))
except EOFError:
break