类型题 Ⅲ:双指针及哈希表求和
类型题 Ⅲ:双指针及哈希表求和
题目类型:在数组中查找和为某个值的元素组合,有可能返回元素组合下标,也有可能只返回组合的个数。
思路一:暴力解法,双重循环,每次固定一个 i
,用 j
循环向前走,判断两数之和是否为 target,若是则返回 i
和 j
的坐标,时间复杂度为
O
(
n
2
)
O(n^2)
O(n2)。
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
if not nums: return []
n = len(nums)
for i in range(n):
for j in range(i+1, n):
if nums[i] + nums[j] == target:
return [i, j]
思路二:哈希表。可以把这个问题想成一个配对儿问题,对于每个元素都有唯一的元素和它配对以形成 target 值。那么在找属于自己的另一半时,其实不用一个一个拉过来问,你是我的另一半吗?可以创建一个登记册,先在登记册找看有没有自己要的人,没有就把自己的信息登记上去,等着自己的另一半来找自己。这样只需要遍历数组一遍,就能得到匹配的两个数了,时间复杂度为
O
(
n
)
O(n)
O(n)。
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
if not nums: return []
n = len(nums)
dict = {<!-- -->}
for i in range(n):
cur = target - nums[i]
if cur in dict:
return [dict[cur], i]
dict[nums[i]] = i
这个暴力法肯定是不行的了,时间复杂度就可太高了,达到了
O
(
n
3
)
O(n^3)
O(n3)。
思路:双指针。两数之和的暴力解法是固定一个下标 i
,用 j
循环遍历找到与 i
位置元素配对的元素。对于三数之和,没办法直接用哈希表加速查找,故使用类似于两数之和的暴力法进行查找。
基本思路是先对原数组按照由小到大进行排序。固定一个下标 k
,用指针 i
和 j
来动态寻找目标解。假设初始时 k = 0
,令 i
和 j
分别指向 k
之后的数组两端,计算三个位置元素之和,若大于 target 就让 j
减 1,小于 target 就让 i
加 1,直到 i
不小于 j
为止,结束之后 k
加 1,开始下一轮判断。
需要注意的是,题目要求i不能包括重复的三元组,所以对于数组中重复的数字需要额外处理,也就是当 i
或 k
加 1,以及 j
减 1 时,要越过和当前位置相同的数字。
时间复杂度:
O
(
n
2
)
O(n^2)
O(n2),其中排序需要
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)<br> **空间复杂度**:
O
(
1
)
O(1)
O(1)
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
n = len(nums)
if n < 3: return []
nums.sort()
res = []
k = 0
while k < n-1:
i, j = k+1, n-1
while i < j:
s = nums[k] + nums[i] + nums[j]
if s < 0:
# 略过相同的数字
while i < j and nums[i+1] == nums[i]: i += 1
i += 1
elif s > 0:
# 略过相同的数字
while i < j and nums[j-1] == nums[j]: j -= 1
j -= 1
else:
res.append([nums[k], nums[i], nums[j]])
while i < j and nums[i + 1] == nums[i]: i += 1
i += 1
while i < j and nums[j - 1] == nums[j]: j -= 1
j -= 1
while k < n-1 and nums[k+1] == nums[k]: k += 1
k += 1
return res
类似的题还有 16. 最接近的三数之和
解题思路一样,先排序,固定一个 k
,用 i
和 j
双指针求解,每次记录当前三数之和并比较与 target 的差的绝对值,记录下绝对值最小时的答案 res
,最后返回 res
即可。
class Solution:
def threeSumClosest(self, nums: List[int], target: int) -> int:
n = len(nums)
if n < 3: return
if n == 3: return sum(nums)
nums.sort()
res = float('inf')
for k in range(n-2):
i, j = k+1, n-1
while i < j:
cur = nums[k] + nums[i] + nums[j]
res = cur if abs(cur-target) < abs(res-target) else res
if cur > target:
j -= 1
elif cur < target:
i += 1
else:
break
if cur == target:
break
return res
参考三数之和的解法,先固定前两个数的解,再用双指针找另外两个数。需要三重循环,时间复杂度为
O
(
n
3
)
O(n^3)
O(n3)。
思路:使用 4 个指针 m、n、i、j,固定 m 和 n,i = n + 1m,j = length -1,移动 i 和 j 包夹求解即可。即最外层是 m 指针遍历数组,嵌套 n 指针遍历 m 指针之后的子数组,再嵌套 i 和 j 双指针包夹求解。
时间复杂度:
O
(
n
3
)
O(n^3)
O(n3),其中排序需要
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)
class Solution:
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
nums.sort()
length = len(nums)
if length < 4: return []
res, m = [], 0
while m < length-3:
n = m + 1
while n < length-2:
i, j = n+1, length-1
while i < j:
s = nums[m] + nums[n] + nums[i] + nums[j]
if s < target:
while i < j and nums[i+1] == nums[i]: i += 1
i += 1
elif s > target:
while i < j and nums[j-1] == nums[j]: j -= 1
j -= 1
else:
res.append([nums[m], nums[n], nums[i], nums[j]])
while i < j and nums[i+1] == nums[i]: i += 1
i += 1
while i < j and nums[j-1] == nums[j]: j -= 1
j -= 1
while n < length-2 and nums[n+1] == nums[n]: n += 1
n += 1
while m < length-3 and nums[m+1] == nums[m]: m += 1
m += 1
return res
思路:求前两个数组每两个元素之间的和,存在哈希表中,键为和,值为等于该和的个数。再求后两个数组每两个元素之间的和,在哈希表中查找该和的相反数。由于该题不要求返回具体的元素下标,所以只需要统计个数即可。
时间复杂度:
O
(
n
2
)
O(n^2)
O(n2),两个双重循环<br> 空间复杂度:
O
(
n
2
)
O(n^2)
O(n2),哈希表需要的额外存储空间
class Solution:
def fourSumCount(self, A: List[int], B: List[int], C: List[int], D: List[int]) -> int:
nA, nB, nC, nD = len(A), len(B), len(C), len(D)
sum, dict = 0, {<!-- -->}
for i in range(nA):
for j in range(nB):
s = A[i] + B[j]
dict[s] = dict.get(s, 0) + 1
for i in range(nC):
for j in range(nD):
s = -(C[i] + D[j])
sum += dict.get(s, 0)
return sum