2-Sum系列解法:
2-Sum:
- hashmap/set(
O(N))- 只可以找确定的
target,速度快,也便于统计target出现次数 - 不便于去重
- 只可以找确定的
- 双指针
- 便于去重
- 可以用来找相近答案
- 可以集合iterator处理tree类 (ie:LC653)
- 可以节省空间(
O(N)->O(h))
- 可以节省空间(
3-Sum / 4-Sum:
- 双指针(本质上都可以简化成2-Sum)
- “k-sum(k >= 3)系列”都建议使用“双指针”
K-Sum:
- 递归
2-Sum
170. 两数之和 III - 数据结构设计(Easy)
Solu:HashMap
ie:比如find(2),但是迄今只有一次add(1),那么虽然key = 2 - 1 = 1存在,但是出现频率至少要≥ 2
Code:
class TwoSum:
def __init__(self):
self.dic = {}
def add(self, number: int) -> None:
self.dic[number] = self.dic.get(number, 0) + 1
def find(self, value: int) -> bool:
for num in self.dic:
if value - num in self.dic and (value - num != num or self.dic[num] > 1):
return True
return False
653. 两数之和 IV - 输入 BST(Easy)
Solu 1:双指针 O(N)
- BST的中序遍历是一个sorted array
- 对一个sorted array用双指针在
O(N)时间内找2-Sum
Code 1:
class Solution:
def findTarget(self, root: Optional[TreeNode], k: int) -> bool:
def inOrder(node) -> List[int]:
if not node:
return []
return inOrder(node.left) + [node.val] + inOrder(node.right)
def find(nums, target) -> bool:
left, right = 0, len(nums) - 1
while left < right:
x = nums[left] + nums[right]
if x == target:
return True
elif x < target:
left += 1
else:
right -= 1
return False
nums = inOrder(root)
return find(nums, k)
Solu 2:空间优化O(N)->O(h):stack构建BST iterator ❤️
- 正向和反向分别建立一个iterator,空间复杂度
O(h),时间复杂度O(N)left++-> 正向iterateright---> 反向iterate
Code 2:
class BSTIterator:
def __init__(self, root: TreeNode, forward: bool):
self.stack = []
self.forward = forward
if forward:
self.pushAllLeft(root)
else:
self.pushAllRight(root)
def next(self) -> int:
node = self.stack.pop()
if self.forward:
self.pushAllLeft(node.right)
else:
self.pushAllRight(node.left)
return node.val
def hasNext(self) -> bool:
return len(self.stack) > 0
def pushAllLeft(self, root):
while root:
self.stack.append(root)
root = root.left
def pushAllRight(self, root):
while root:
self.stack.append(root)
root = root.right
def peek(self) -> int:
return self.stack[-1].val
class Solution:
def findTarget(self, root: Optional[TreeNode], k: int) -> bool:
l, r = BSTIterator(root, True), BSTIterator(root, False)
while l.hasNext() and r.hasNext() and l.peek() < r.peek():
x = l.peek() + r.peek()
if x == k:
return True
elif x < k:
l.next() # 变大
else:
r.next() # 变小
return False
3-Sum
套路:第一个指针i切开,右侧arraynums[i+1:]用双指针做2-sum
15. 三数之和(Medium)
Solu:双指针
3次去重:
- 2-Sum前:对当前基准
nums[i]去重 - 2-Sum时:对
nums[left]和nums[right]分别去重
2次剪枝:
- if 当前
max = nums[i] + nums[-1] + nums[-2] < 0, thennums[i]太小,直接跳到下一轮 - if 当前
min = nums[i] + nums[i + 1] + nums[i + 2] > 0, thennums[i]太大,后面的都不用算了
Code:
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
nums.sort()
res = []
for i in range(len(nums) - 2):
if nums[i] + nums[-1] + nums[-2] < 0: # 剪枝
continue
if nums[i] + nums[i + 1] + nums[i + 2] > 0: # 剪枝
break
if i > 0 and nums[i] == nums[i - 1]: # 去重nums[i]
continue
# 2-Sum
l, r = i + 1, len(nums) - 1
while l < r:
total = nums[i] + nums[l] + nums[r]
if total == 0:
res.append([nums[i], nums[l], nums[r]])
l += 1
r -= 1
while l < r and nums[l] == nums[l - 1]: # 去重nums[l]
l += 1
while l < r and nums[r] == nums[r + 1]: # 去重nums[r]
r -= 1
elif total > 0:
r -= 1
else:
l += 1
return res
16. 最接近的三数之和(Medium)
Solu:双指针
2次剪枝同上,略
只要当前sum != target,即使abs(sum - target) < abs(cur_closest - target),也要继续尝试令sum逼近target
Code:
class Solution:
def threeSumClosest(self, nums: List[int], target: int) -> int:
nums.sort()
closest = nums[0] + nums[1] + nums[2]
for i in range(len(nums) - 2):
if i > 0 and nums[i] == nums[i - 1]: # skip duplicates
continue
if nums[i] + nums[i + 1] + nums[i + 2] > target: # 剪枝
if nums[i] + nums[i + 1] + nums[i + 2] - target < abs(closest - target):
closest = nums[i] + nums[i + 1] + nums[i + 2]
break
if nums[i] + nums[-1] + nums[-2] < target: # 剪枝
if target - (nums[i] + nums[-1] + nums[-2]) < abs(closest - target):
closest = nums[i] + nums[-1] + nums[-2]
continue
l, r = i + 1, len(nums) - 1
while l < r:
total = nums[i] + nums[l] + nums[r]
if total == target:
return target
else:
if abs(total - target) < abs(closest - target):
closest = total
# 继续尝试逼近target
if total > target:
r -= 1
else:
l += 1
return closest
259. 较小的三数之和(Medium)
Solu:双指针
- 如果
nums[i]+nums[l]+nums[r] < target,说明对于任意j满足l+1 ≤ j ≤ r,都有nums[i]+nums[l]+nums[j] < target。这样的j共有r - (l+1) + 1 = r - l个
2次剪枝:
- if
min = nums[i] + nums[i + 1] + nums[i + 2] >= target,那么后面的也没必要算了,必定不满足条件 - if
max = nums[i] + nums[-1] + nums[-2] < target,那么只要对于当前nums[i],只要在subArraynums[i+1:]中任选两个元素,都可以满足3-sum < target
Code:
class Solution:
def threeSumSmaller(self, nums: List[int], target: int) -> int:
nums.sort()
count = 0
for i in range(len(nums) - 2):
if nums[i] + nums[i + 1] + nums[i + 2] >= target: # 剪枝
break
if nums[i] + nums[-1] + nums[-2] < target: # 剪枝
length = len(nums) - i - 1
count += length * (length - 1) // 2
continue
l, r = i + 1, len(nums) - 1
while l < r:
total = nums[i] + nums[l] + nums[r]
if total < target:
count += r - l
l += 1
else:
r -= 1
return count
923. 三数之和的多种可能(Medium)
Solu 1:HashMap + 3Sum传统做法
- 对于每个
arr[i],在subarrayarr[i+1 : ]中统计2-sum == target - arr[i]的个数
Code 1:
class Solution:
def threeSumMulti(self, arr: List[int], target: int) -> int:
def twoSum(start, end, k):
dic = {}
count = 0
for i in range(start, end):
count += dic.get(k - arr[i], 0)
dic[arr[i]] = dic.get(arr[i], 0) + 1
return count
mod = 10 ** 9 + 7
res = 0
arr.sort()
for i in range(len(arr)):
if i < len(arr) - 2 and arr[i] + arr[i + 1] + arr[i + 2] > target: # prune
break
if arr[i] + arr[-1] + arr[-2] < target: # prune
continue
res = (res + twoSum(i + 1, len(arr), target - arr[i])) % mod # count
return res
Solu 2:
- 统计对于当前
arr[i],在subarrayarr[ : i]中2-sum = target - arr[i]出现的次数 - 为了下一轮loop的统计,更新当前
arr[j] + arr[i] (0 ≤ j < i)出现的次数
Code 2:
class Solution:
def threeSumMulti(self, arr: List[int], target: int) -> int:
mod = 10 ** 9 + 7
count = 0
dic = {}
for i in range(len(arr)):
count = (count + dic.get(target - arr[i], 0)) % mod # count
for j in range(i): # update dict
dic[arr[i] + arr[j]] = dic.get(arr[i] + arr[j], 0) + 1
return count
Solu 3:hashMap + 组合 ❤️
- 先统计每个
num在arr中出现的frequency - 任选三个数字,以下三种case时值得考虑的(避免重复统计):
num1 == num2 == num3->C(#num1, 3)num1 == num2 != num3->C(#num1, 2) * #num3num1 < num2 < num3->#num1 * #num2 * #num3
Code 3:
class Solution:
def threeSumMulti(self, arr: List[int], target: int) -> int:
mod = 10 ** 9 + 7
count = 0
dic = collections.Counter(arr)
for num1 in dic.keys():
for num2 in dic.keys():
num3 = target - num1 - num2
cnt1, cnt2, cnt3 = dic[num1], dic[num2], dic[num3]
if num1 == num2 == num3: # 如果三个数相等,则相当于cnt1中任选三个
count += cnt1 * (cnt1 - 1) * (cnt1 - 2) // 6
elif num1 == num2: # 如果num1==num2,则相当于cnt1中任选两个
count += cnt1 * (cnt1 - 1) // 2 * cnt3
elif num1 < num2 < num3:
count += cnt1 * cnt2 * cnt3
count %= mod
return count
4-Sum
18. 四数之和(Medium)
Solu:
- 和3-sum的思路完全一致,不过现在是暴力枚举前两位数字
nums[i]和nums[j],然后在subarraynums[j+1 : ]上用“双指针”做2-sum - 剪枝的思路也和3-sum一致,略
Code:
class Solution:
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
nums.sort()
res = []
for i in range(len(nums) - 3):
if i > 0 and nums[i] == nums[i - 1]: # 去重
continue
if sum(nums[i:i + 4]) > target: # 剪枝
break
if sum(nums[-3:]) < target - nums[i]: # 剪枝
continue
for j in range(i + 1, len(nums) - 2):
if j > i + 1 and nums[j] == nums[j - 1]: # 剪枝
continue
if sum(nums[j:j + 3]) > target - nums[i]: # 剪枝
break
if sum(nums[-2:]) < target - nums[i] - nums[j]: # 去重
continue
# 2-sum
l, r = j + 1, len(nums) - 1
while l < r:
total = nums[i] + nums[j] + nums[l] + nums[r]
if total == target:
res.append([nums[i], nums[j], nums[l], nums[r]])
l += 1
r -= 1
while l < r and nums[l] == nums[l - 1]:
l += 1
while l < r and nums[r] == nums[r + 1]:
r -= 1
elif total < target:
l += 1
else:
r -= 1
return res
454. 四数相加 II(Medium)
Solu:
- 对于
∀x in nums1和∀y in nums2,统计所有x+y出现的频率 - 对于
∀m in nums3和∀n in nums4,因为满足x+y+m+n=0,所以找x+y=-(m+n)出现的频率
Code:
class Solution:
def fourSumCount(self, nums1: List[int], nums2: List[int], nums3: List[int], nums4: List[int]) -> int:
dic = {}
for x in nums1:
for y in nums2:
dic[x + y] = dic.get(x + y, 0) + 1
res = 0
for m in nums3:
for n in nums4:
res += dic.get(-m - n, 0)
return res
K-Sum
- 如果只要求k-sum == target的数量(不要求内部具体是哪些数字),那么可以转化为01背包问题
- 如果要求列出具体是有哪些数字,则DFS暴力法
89 · K数之和(Hard)
Solu 1:DP - 01背包
转化为01背包问题
dp[i][j][k]= #前i个元素里选j个数使得sum == k的方案dp[i][j][k] = dp[i-1][j-1][k-A[i]] (if k ≥ A[i]) + dp[i-1][j][k]
Code 1:滚动数组优化
class Solution:
def kSum(self, A, k, target):
n = len(A)
dp = [[0] * (target + 1) for _ in range(k + 1)]
dp[0][0] = 1
for i in range(n):
for j in range(k, 0, -1):
for w in range(target, A[i] - 1, -1):
dp[j][w] += dp[j - 1][w - A[i]]
return dp[k][target]
Solu 2:DFS记忆化递归
(start_idx, 还需要添加多少个数字, 还剩多少才能达到target)标识一个状态- 把问题DFS不断规约(K Sum -> K-1 Sum),直至
k == 0或者变为2-Sum
Code 2:
class Solution:
def __init__(self):
self.memo = {}
def kSum(self, A, k, target):
A.sort()
return self.dfs(A, k, target, 0)
def dfs(self, A, k, remain, idx) -> int:
if k == 0:
return 1 if remain == 0 else 0
if remain < 0: # pruning
return 0
key = (idx, k, remain)
if key in self.memo:
return self.memo[key]
res = 0
for i in range(idx, len(A)):
res += self.dfs(A, k - 1, remain - A[i], i + 1)
self.memo[key] = res
return res
或者DFS将K-Sum不断削弱至2-Sum
class Solution:
def __init__(self):
self.memo = {}
def kSum(self, A, k, target):
A.sort()
if k < 2:
return A.count(target)
return self.dfs(A, k, target, 0)
def dfs(self, A, k, remain, idx) -> int:
if remain < 0 or A[-1] * k < remain: # pruning
return 0
if k == 2:
return self.twoSum(A, remain, idx)
key = (idx, k, remain)
if key in self.memo:
return self.memo[key]
res = 0
for i in range(idx, len(A)):
res += self.dfs(A, k - 1, remain - A[i], i + 1)
self.memo[key] = res
return res
def twoSum(self, nums, target, idx):
count = 0
l, r = idx, len(nums) - 1
while l < r:
total = nums[l] + nums[r]
if total == target:
count += 1
l += 1
r -= 1
elif total < target:
l += 1
else:
r -= 1
return count
89 · K数之和(Meidum)
Solu 1:DFS回溯
- 类似于在做subset的展开,暴力展开所有解
Code 1:
class Solution:
def __init__(self):
self.ans = []
def kSumII(self, A, k, target):
A.sort()
self.dfs(A, k, target, 0, [])
return self.ans
def dfs(self, A, k, remain, idx, path):
if k == 0:
if remain == 0:
self.ans.append(path[:])
return
if remain < 0: # 剪枝
return
for i in range(idx, len(A)):
path.append(A[i])
self.dfs(A, k - 1, remain - A[i], i + 1, path)
path.pop()
Solu 2:DFS递归(不回溯)
- DFS递归地去约化问题为(K Sum -> K-1 Sum),直至问题被削弱为2-sum
- PS:需要单独处理
k == 1的case
- PS:需要单独处理
- 剪枝 x2:如果
最小的数 * k > target或者最大的数 * k < target,可以提前停止
Code 2:
class Solution:
def kSumII(self, A, k, target):
A.sort()
if k < 2:
return [[target]] if target in A else []
return self.dfs(A, k, target, 0, [])
def dfs(self, A, k, remain, idx, path):
res = []
if idx == len(A):
return res
if k * A[idx] > remain or k * A[-1] < remain: # pruning
return res
if k == 2:
return self.twoSum(A, remain, idx)
for i in range(idx, len(A)):
for path in self.dfs(A, k - 1, remain - A[i], i + 1, path + [A[i]]):
res.append([A[i]] + path)
return res
def twoSum(self, nums, target, idx):
res = []
l, r = idx, len(nums) - 1
while l < r:
total = nums[l] + nums[r]
if total == target:
res.append([nums[l], nums[r]])
l += 1
r -= 1
elif total < target:
l += 1
else:
r -= 1
return res
Reference: