作为 Leetcode 的第一题,两数之和自然是知名度最高的,从两数之和出发也有不少的衍生题目,下面就让我们好好地解决它们。
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
record = dict()
for i, num in enumerate(nums):
if target - num in record:
return [record[target - num], i]
else:
record[num] = i
最直接的思路是用哈希表,把出现过的数字(及其索引下标)都用哈希表记录下来,这样每当遍历新的数字,就能快速找出 target - num 是否出现过,若是则返回下标。
class TwoSum:
def __init__(self):
self.record = dict()
def add(self, number: int) -> None:
if number in self.record:
self.record[number] += 1
else:
self.record[number] = 1
def find(self, value: int) -> bool:
for num in self.record.keys():
if value - num != num:
if value - num in self.record:
return True
elif self.record[num] > 1:
return True
return False
这题是不断地向数组添加数字,同时得能够检查数字是否为数组中某两个数字之和,思路还是哈希表,每当要对数字进行检查时,这个数字就相当于 target,遍历数组中的数字 num,找到 target - num 是否也在数组中即可。要注意的是可能出现 target - num 等于 num 的情况,而这种情况得 num 出现过两次才能返回 True,所以要用字典统计 num 出现的次数。
class Solution:
def twoSum(self, numbers: List[int], target: int) -> List[int]:
n = len(numbers)
left = 0
right = n - 1
while left < right:
total = numbers[left] + numbers[right]
if total > target:
right -= 1
elif total < target:
left += 1
else:
return [left+1, right+1]
相较于上一题,这题的条件是数组已经按非递减顺序排列,这其实也给了我们一个新的思路,因为有序数组总是与双指针联系在一起。所以说,这题我们可以使用左右双指针 left 与 right,当两数之和大于 target 时右指针左移,当两数之和小于 target 时左指针右移,正好等于 target 时返回答案(注意下标从 1 开始)。
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
ans = []
n = len(nums)
nums.sort()
for i in range(n):
# 除了首位置之外,如果有多个重复的 nums[i],那就只考虑最后一个
if i > 0 and nums[i] == nums[i - 1]:
continue
left = i + 1
right = n - 1
while left < right:
total = nums[i] + nums[left] + nums[right]
# total 太大,右指针左移
if total > 0:
right -= 1
# total 太小,左指针右移
elif total < 0:
left += 1
else:
# total 符合条件
ans.append([nums[i], nums[left], nums[right]])
# 避免 left + 1 与 left 一样
while left < right and nums[left] == nums[left + 1]:
left += 1
# 避免 right - 1 与 right 一样
while left < right and nums[right] == nums[right - 1]:
right -= 1
# 确保了不会出现重复的答案
left += 1
right -= 1
return ans
从上一题得到的灵感,就是可以将数组排序然后用双指针解法,这对于三数、四数甚至更高的数之和都是适用的。在本题中,我们首先遍历并固定第一个下标 i,然后对它后面的区域进行双指针查找。由于不能出现重复的三元组,所以去重点有两个:
1、除了第一个位置以外,凡是 nums[i] == nums[i-1]
的,就说明 i 这个位置的数与上一个位置的重复了,直接考虑 i + 1。如果用的是 nums[i] == nums[i+1]
判断,就会把如 (-1, -1, 2) 这样的答案给忽略掉。
2、每当找到一个答案以后,left 和 right 指针都要移动,而它们应该跳过与当前位置数字相同的位置,避免出现重复的三元组。
class Solution:
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
ans = []
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):
if j > i+1 and nums[j] == nums[j-1]:
continue
left = j + 1
right = n - 1
while left < right:
total = nums[i] + nums[j] + nums[left] + nums[right]
if total > target:
right -= 1
elif total < target:
left += 1
else:
ans.append([nums[i], nums[j], nums[left], nums[right]])
while left < right and nums[left] == nums[left+1]:
left += 1
while left < right and nums[right] == nums[right-1]:
right -= 1
left += 1
right -= 1
return ans
与三数之和相比,仅仅是多遍历了一个 j,注意 j 的位置是从 i + 1 开始的,所以它的去重是 if j > i+1 and nums[j] == nums[j-1]: