一.两数之和
两数之和有两种题目:一种是输入的列表中的元素是有序的,一种是列表中的元素是无序的。
如果是要我们返回原列表中元素的下标而不是值的话,那么无序的情况就不能使用双指针的做法
因为双指针的做法前提是有序,无序的列表一旦排序,那么它原本元素对应的下标就丢失了
1.无序的列表-两数之和
给定一个整数数组 nums
和一个整数目标值 target
,请你在该数组中找出 和为目标值 target
的那 两个 整数,并返回它们的数组下标。
做法是使用哈希表。
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
# 理解了官方题解的做法:哈希表
# 哈希表能有一一对应的关系,我们让 值对应下标 来存储进哈希表
# 用字典来实现哈希表
hash = dict()
for i,num in enumerate(nums):
if target - num in hash:
return [hash[target-num],i]
else:hash[nums[i]] = i # 还没找到,把这个值放进哈希表
# 也就是 键是数值,值是下标
return []
2. 有序列表-两数之和
题目链接:167. 两数之和 II - 输入有序数组 - 力扣(LeetCode)
给你一个下标从 1 开始的整数数组 numbers
,该数组已按 非递减顺序排列 ,请你从数组中找出满足相加之和等于目标数 target
的两个数。如果设这两个数分别是 numbers[index1]
和 numbers[index2]
,则 1 <= index1 < index2 <= numbers.length
。
以长度为 2 的整数数组 [index1, index2]
的形式返回这两个整数的下标 index1
和 index2
。
你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。
你所设计的解决方案必须只使用常量级的额外空间。
列表是有序的,我们可以使用双指针的方式代替使用双循环这种暴力破解方法
class Solution:
def twoSum(self, numbers: List[int], target: int) -> List[int]:
n = len(numbers)
head = 0
tail = n-1
# 头是第一个元素,尾是最后一个元素
while head < tail:
res = numbers[head] + numbers[tail]
# 已排序的情况下,头+尾 大于目标值,头往后的所有值与尾相加都大于目标值,那么尾巴变小(往前走一步)
# 头 + 尾 小于 目标值,尾往前的所有值与头相加都小于目标值,头变大(往后走一步)
if res > target:
tail -= 1
elif res < target:
head += 1
else:
return [head+1, tail+1]
# 注意题目中的下标是从一开始的。我们的head和tail是索引。
return []
二.三数之和
给你一个整数数组 nums
,判断是否存在三元组 [nums[i], nums[j], nums[k]]
满足 i != j
、i != k
且 j != k
,同时还满足 nums[i] + nums[j] + nums[k] == 0
。请
你返回所有和为 0
且不重复的三元组。
注意:答案中不可以包含重复的三元组。
题目并不是要我们返回下标而是返回值就可以,所以我们可以先将列表进行排序,排完序之后,枚举一个个数,那么剩下的那两个数转化成了两数之和问题,就可以使用双指针来寻找。
class Solution:
def threeSum(self, nums: list) -> list:
# 要找到三个数,我们枚举第一个数,剩下那两个数,就相当于两数之和的情况了,target = -(第一个数)
nums.sort() # 减少重复情况的判断
n = len(nums)
ans = []
for first in range(n - 2):
# 最后是n-1,我们这一个for只枚举第一个数,故只到n-3即可
x = nums[first]
if first > 0 and x == nums[first - 1]: continue
# 已经排序过了,遇到重复的不用再走一遍了
if x + nums[first+1] + nums[first+2] >0 : break
if x + nums[-2] + nums[-1] < 0 : continue
# 这两个if在面对大量的测试用例时可以减少总的运行时间
# 已经排序了的情况下 如果 第一个位置和紧接着的两个位置 的和 > 0证明往后任何的组合都大于0,可以直接不用找了
# 第一个位置和和倒数两个位置的和< 0的话,那么可能就是第一个位置太小,第一个位置向前走变大的话还是有可能找到符合的情况的。所以跳过这个情况找下一个
# 这两个if 排掉了一些不可能有结果的情况,减少多个测试用例时的总运行时间
second = first + 1
third = n - 1 # 直接从最后一个往回找起
while second < third:
res = x + nums[second] + nums[third]
# 已排序的情况下,头+尾 大于目标值,头往后的所有值与尾相加都大于目标值,那么尾巴变小(往前走一步)
# 头 + 尾 小于 目标值,尾往前的所有值与头相加都小于目标值,头变大(往后走一步)
if res > 0:
third -= 1
elif res < 0:
second += 1
else:
ans.append([x, nums[second], nums[third]])
second += 1
while second < third and nums[second] == nums[second - 1]: second += 1
# 一样的,碰到一样的就跳过
third -= 1
# 因为这个时候third还是和上一个second相加等于目标值的数,second+=1之后,third与新second
# 相加一定大于目标值,所以直接往前走
while second < third and nums[third] == nums[third + 1]: third -= 1
# 至此新的头和尾设定完成,继续执行
return ans
多这两个条件判断的执行时间和帮我们做无用功的时间相比,就是优化。能让总体运行时间更快
if x + nums[first+1] + nums[first+2] >0 : break
if x + nums[-2] + nums[-1] < 0 : continue
# 这两个if在面对大量的测试用例时可以减少总的运行时间
# 已经排序了的情况下 如果 第一个位置和紧接着的两个位置 的和 > 0证明往后任何的组合都大于0,可以直接不用找了
# 第一个位置和和倒数两个位置的和< 0的话,那么可能就是第一个位置太小,第一个位置向前走变大的话还是有可能找到符合的情况的。所以跳过这个情况找下一个
# 这两个if 排掉了一些不可能有结果的情况,减少多个测试用例时的总运行时间
三.四数之和
给你一个由 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
你可以按 任意顺序 返回答案 。
我们枚举第一个数和第二个数,剩下两个 数再次变成了两数之和的问题
需要特别注意的地方:
for second in range(first+1, n-2):
two = nums[second]
if second > first+1 and two == nums[second-1]:continue
每个位置的枚举的第一次至少要走完,再去判断第二次是不是和第一次一样,否者直接跳完了,ans就是[]了,所以 second > first +1 不能漏掉
class Solution:
def fourSum(self, nums:list, target:int) -> list:
n = len(nums)
nums.sort()
ans = []
for first in range(n - 3):
one = nums[first]
if first > 0 and one == nums[first-1]:continue
if one + nums[first+1] +nums[first+2] + nums[first+3] > target:break
if one + nums[-3] +nums[-2] + nums[-1] < target:continue
# 避免无用功
for second in range(first+1, n-2):
two = nums[second]
if second > first+1 and two == nums[second-1]:continue
if one + two + nums[second+1] + nums[second+2] > target: break
if one + two + nums[-2] + nums[-1] < target: continue
# 避免无用功
head = second + 1
tail = n-1
while head < tail:
target_double = target - nums[first] - nums[second]
res = nums[head] + nums[tail]
if res < target_double:
head += 1
elif res > target_double:
tail -= 1
else:
ans.append([ one, two, nums[head], nums[tail] ])
head += 1
while head < tail and nums[head] == nums[head-1]:head += 1
tail -= 1
while head < tail and nums[tail] == nums[tail+1]: tail -= 1
# 至此新的头尾设定完成,继续执行
return ans
注意,四数之和在枚举的第一和第二个位置中仍可以像前面三数之和一样做优化,避免无用功。总体运行时间得到巨大优化!