Skip to main content

Two Sum

Narrative

The hash map is optimal for exactly one problem in this family. Every later one either hands you a property that makes the map unnecessary (sorted input) or asks for something the map can't give (every distinct tuple).

At each step, ask two things: what does the input let me assume, and what shape is the answer?

Unsorted input, so pay for a hash map​

Otto, your interviewer

1. Two Sum​

1

Two Sum

LeetCode
Easy

Brute Force

FIG. 1 TWO SUM ALL PAIRS● INTERACTIVE
visualization loads as you reach it
class Solution:
def twoSum(self, nums: list[int], target: int) -> list[int]:
n = len(nums)
for i in range(n):
for j in range(i + 1, n):
if nums[i] + nums[j] == target:
return [i, j]

Duplicates, collisions, and when sorting wins

Duplicates overwrite, collisions crowd: only crowding hurts. A duplicate key overwrites its own entry in O(1). A collision is two different keys in one bucket; enough of them make every operation O(n). Say it as: "Expected O(n), worst O(n2) under adversarial collisions."

When sorting beats the map. The map is O(n) expected. With adversarial collisions every lookup costs O(n), so its worst case is O(n2). Sorting guarantees O(n log n), the best worst case available here. The catch: LeetCode 1 returns original indices and sorting scrambles them, so sort (value, index) pairs (O(n) extra space), then run two pointers. Once the input arrives sorted (167), the sort is free and the map loses outright.

top ↑

Sorted input replaces the hash map​

Otto, your interviewer

167. Two Sum II - Input Array Is Sorted​

167

Two Sum II - Input Array Is Sorted

LeetCode
Medium

2 Converging Pointers

Borrowed from Two Pointers ↗
FIG. 167 TWO SUM II INPUT ARRAY IS SORTED● INTERACTIVE
visualization loads as you reach it
class Solution:
def twoSum(self, numbers: List[int], target: int) -> List[int]:
left, right = 0, len(numbers) - 1
while left < right:
total = numbers[left] + numbers[right]
if total < target:
left += 1
elif total > target:
right -= 1
else:
return [left + 1, right + 1]
return [-1, -1]
top ↑

The approach ladder​

ApproachTimeWhen you say it
Aux space
Brute ForceO(n2)opening line only
O(1)
1-Pass Index Map (from 1)O(n) expectedignores the sort
O(n)
Fix One, Bisect AnotherO(n log n)lopsided sizes or many queries
O(1)
2 Converging PointersO(n)the expected answer
O(1)

Pairs less than target​

Otto, your interviewer

2824. Count Pairs Whose Sum is Less than Target​

2824

Count Pairs Whose Sum is Less than Target

LeetCode
Easy

Brute Force

FIG. 2824 COUNT PAIRS WHOSE SUM IS LESS THAN TARGET BRUTE FORCE● INTERACTIVE
visualization loads as you reach it
class Solution:
def countPairs(self, nums: List[int], target: int) -> int:
n = len(nums)
ans = 0
for i in range(n):
for j in range(i + 1, n):
if nums[i] + nums[j] < target:
ans += 1
return ans

Every approach

ApproachTimeWhen an interviewer wants it
Aux space
Brute ForceO(n2)opening line
O(1)
Sort + Bisect per iO(n log n)"without two pointers"
O(sort)
Sort + 2 Converging PointersO(n log n)the expected answer
O(sort)
Count by ValueO(n + V2)when the tiny value range is the point
O(V)
top ↑
Otto, your interviewer

1099. Two Sum Less Than K​

1099

Two Sum Less Than K

LeetCode
Easy

Given an array nums of integers and an integer k, return the maximum sum such that there exists i < j with nums[i] + nums[j] = sum and sum < k. If no i, j exist satisfying this equation, return -1.

  • nums = [34, 23, 1, 24, 75, 33, 54, 8], k = 60 returns 58: 34 + 24.
  • nums = [10, 20, 30], k = 15 returns -1: no pair sums below 15.
  • Constraints: 1 <= nums.length <= 100, 1 <= nums[i] <= 1000, 1 <= k <= 2000.

Brute Force

FIG. 1099 TWO SUM LESS THAN K BRUTE FORCE● INTERACTIVE
visualization loads as you reach it
class Solution:
def twoSumLessThanK(self, nums: list[int], k: int) -> int:
n = len(nums)
ans = -1
for i in range(n):
for j in range(i + 1, n):
total = nums[i] + nums[j]
if total < k:
ans = max(ans, total)
return ans

Every approach

ApproachTimeWhen an interviewer wants it
Aux space
Brute ForceO(n2)opening line
O(1)
Sort + Fix One, Bisect AnotherO(n log n)"without two pointers"
O(sort)
Sort + 2 Converging PointersO(n log n)the expected answer
O(sort)
Count by ValueO(n + D2)when the small value range is the point
O(D)
top ↑

Pairs in a range​

Otto, your interviewer

2563. Count the Number of Fair Pairs​

2563

Count the Number of Fair Pairs

LeetCode
Medium

Given a 0-indexed integer array nums of size n and two integers lower and upper, return the number of fair pairs. A pair (i, j) is fair if 0 <= i < j < n and lower <= nums[i] + nums[j] <= upper.

  • nums = [0, 1, 7, 4, 4, 5], lower = 3, upper = 6 returns 6: pairs (0, 3), (0, 4), (0, 5), (1, 3), (1, 4) and (1, 5).
  • nums = [1, 7, 9, 2, 5], lower = 11, upper = 11 returns 1: pair (2, 3).
  • Constraints: 1 <= nums.length <= 105, -109 <= nums[i] <= 109, -109 <= lower <= upper <= 109.

Brute Force

FIG. 2563 COUNT THE NUMBER OF FAIR PAIRS BRUTE FORCE● INTERACTIVE
visualization loads as you reach it
class Solution:
def countFairPairs(self, nums: List[int], lower: int, upper: int) -> int:
n = len(nums)
ans = 0
for i in range(n):
for j in range(i + 1, n):
if lower <= nums[i] + nums[j] <= upper:
ans += 1
return ans

Every approach

ApproachTimeWhen an interviewer wants it
Aux space
Brute ForceO(n2)opening line
O(1)
Sort + Count TwiceO(n log n)the expected answer
O(sort)
top ↑

Every distinct triplet​

Otto, your interviewer

15. 3Sum​

15

3Sum

LeetCode
Medium

Brute Force

FIG. 15 3SUM ALL TRIPLETS● INTERACTIVE
visualization loads as you reach it
class Solution:
def threeSum(self, nums: list[int]) -> list[list[int]]:
n = len(nums)
seen = set()
for i in range(n):
for j in range(i + 1, n):
for k in range(j + 1, n):
if nums[i] + nums[j] + nums[k] == 0:
seen.add(tuple(sorted((nums[i], nums[j], nums[k]))))
return list(seen)
top ↑
GOTCHAWork is O(n3), answers are O(n2)

Not reporting a triplet twice​

  • After a match, move both pointers, then step each past values equal to the one just used.
  • Skip i when nums[i] equals the previous fixed value.
  • Sorted, so break once nums[i] > 0: no sum can reach 0.

Every 3Sum approach is an earlier one​

ApproachBuilt fromTime
Aux space
Brute Forcesorted-tuple setO(n3)
O(T)
Sort + Fix Two, Bisect the Third167: Fix One, Bisect AnotherO(n2 log n)
O(sort)
Fix One + 1-Pass Index Map1: 1-Pass Index MapO(n2) expected
O(n)
Sort + 2 Converging Pointers167: 2 Converging PointersO(n2)
O(sort)

Beating O(n2) is believed impossible (the 3SUM conjecture).

3Sum's relatives​

Narrative

Same sorted array, same scan as 3Sum. Only the question changes: count the triples under a target, count the triples that form a triangle, find the nearest sum, or count with repeated values.

Otto, your interviewer

259. 3Sum Smaller​

259

3Sum Smaller

LeetCode
Medium

Brute Force

FIG. 259 3SUM SMALLER BRUTE FORCE● INTERACTIVE
visualization loads as you reach it
class Solution:
def threeSumSmaller(self, nums: list[int], target: int) -> int:
n = len(nums)
ans = 0
for i in range(n):
for j in range(i + 1, n):
for k in range(j + 1, n):
if nums[i] + nums[j] + nums[k] < target:
ans += 1
return ans

Every approach

ApproachTimeWhen an interviewer wants it
Aux space
Brute ForceO(n3)opening line
O(1)
Sort + Fix Two, Bisect the ThirdO(n2 log n)"without two pointers"
O(sort)
Sort + 2 Converging PointersO(n2)the expected answer, and the common follow-up to 3Sum
O(sort)
top ↑
Otto, your interviewer

611. Valid Triangle Number​

611

Valid Triangle Number

LeetCode
Medium

Brute Force

FIG. 611 VALID TRIANGLE NUMBER BRUTE FORCE● INTERACTIVE
visualization loads as you reach it
class Solution:
def triangleNumber(self, nums: list[int]) -> int:
nums.sort()
n = len(nums)
ans = 0
for i in range(n):
for j in range(i + 1, n):
for k in range(j + 1, n):
if nums[i] + nums[j] > nums[k]:
ans += 1
return ans

Every approach

ApproachTimeWhen an interviewer wants it
Aux space
Brute ForceO(n3)opening line
O(sort)
Sort + Fix Two, Bisect the ThirdO(n2 log n)"without two pointers"
O(sort)
Sort + Fix Largest + 2 Converging PointersO(n2)the expected answer
O(sort)
top ↑
Otto, your interviewer

16. 3Sum Closest​

16

3Sum Closest

LeetCode
Medium

Sort + Fix Two, Bisect the Third

Borrowed from Binary Search ↗
FIG. 16 3SUM CLOSEST BISECT● INTERACTIVE
visualization loads as you reach it
class Solution:
def threeSumClosest(self, nums: list[int], target: int) -> int:
nums.sort()
ans = float("inf")
n = len(nums)
for i in range(n):
for j in range(i + 1, n):
comp = target - nums[i] - nums[j]
idx = bisect.bisect_left(nums, comp, j + 1, n)
for k in [idx, idx - 1]:
if j < k < n:
total = nums[i] + nums[j] + nums[k]
if abs(target - ans) > abs(target - total):
ans = total
return ans

Every approach

ApproachTimeWhen an interviewer wants it
Aux space
Brute ForceO(n3)opening line
O(1)
Sort + Fix Two, Bisect the ThirdO(n2 log n)"without two pointers"
O(sort)
Sort + 2 Converging PointersO(n2)the expected answer
O(sort)
Hash mapdoes not work"why no hash version?": hashing answers exact membership, not nearest
-
top ↑
Otto, your interviewer

923. 3Sum With Multiplicity​

923

3Sum With Multiplicity

LeetCode
Medium

Brute Force

FIG. 923 3SUM WITH MULTIPLICITY BRUTE FORCE● INTERACTIVE
visualization loads as you reach it
class Solution:
def threeSumMulti(self, arr: list[int], target: int) -> int:
n = len(arr)
ans = 0
for i in range(n):
for j in range(i + 1, n):
for k in range(j + 1, n):
ans += arr[i] + arr[j] + arr[k] == target
return ans % ((10**9) + 7)

Every approach

ApproachTimeWhen an interviewer wants it
Aux space
Brute ForceO(n3)opening line
O(1)
Fix One + Counting MapO(n2)simplest correct answer
O(n)
Sort + 2 Converging PointersO(n2)"with two pointers"
O(sort)
Count by ValueO(n + 1012)when the small value range is the point
O(101)
top ↑

Every distinct quadruplet​

Otto, your interviewer

18. 4Sum​

18

4Sum

LeetCode
Medium

Brute Force

FIG. 18 4SUM BRUTE FORCE● INTERACTIVE
visualization loads as you reach it
class Solution:
def fourSum(self, nums: list[int], target: int) -> list[list[int]]:
n = len(nums)
seen = set()
for i in range(n):
for j in range(i + 1, n):
for k in range(j + 1, n):
for l in range(k + 1, n):
if nums[i] + nums[j] + nums[k] + nums[l] == target:
seen.add(tuple(sorted((nums[i], nums[j], nums[k], nums[l]))))
return list(seen)

Every approach

ApproachTimeWhen an interviewer wants it
Aux space
Brute ForceO(n4)opening line
O(Q)
Fix Two + 1-Pass Index MapO(n3)when the input can't be sorted
O(n + Q)
Sort + Fix Three, Bisect the FourthO(n3 log n)"without two pointers"
O(sort + Q)
Sort + Fix Two + 2 Converging PointersO(n3)the expected answer
O(sort)
Recursive kSumO(nk-1)"how would you do 5Sum?"
O(k + sort)
top ↑

Separate arrays​

Otto, your interviewer

454. 4Sum II​

454

4Sum II

LeetCode
Medium

Meet in the Middle (2 + 2)

Borrowed from Hash & Set ↗
FIG. 454 4SUM II MEET IN THE MIDDLE● INTERACTIVE
visualization loads as you reach it
class Solution:
def fourSumCount(
self, nums1: list[int], nums2: list[int], nums3: list[int], nums4: list[int]
) -> int:
seen = collections.Counter()
ans = 0
for a in nums1:
for b in nums2:
seen[a + b] += 1
for c in nums3:
for d in nums4:
complement = -c - d
ans += seen[complement]
return ans

Every approach

ApproachTimeWhen an interviewer wants it
Aux space
Brute ForceO(n4)opening line
O(1)
Count one array (1 + 3)O(n3)"memory is tight"
O(n)
Meet in the Middle (2 + 2)O(n2)the expected answer
O(n2)
Sorted Pair Sums + 2 Converging PointersO(n2 log n)"no hashing"
O(n2)
Generalized kSum CountO(nk/2)"what about 6 arrays?"
O(nk/2)
top ↑
Otto, your interviewer

kSum Count​

kSum Count

Hard

Given k integer arrays arrays[0], ..., arrays[k - 1], each of length n, return the number of tuples (i_0, ..., i_{k-1}) such that arrays[0][i_0] + arrays[1][i_1] + ... + arrays[k-1][i_{k-1}] == 0.

This generalizes 454. 4Sum II from four arrays to any k. The solution below is written against the 4-array signature, and kSum(arrays) is the general part.

Example 1:

Input: k = 4, arrays = [[1, 2], [-2, -1], [-1, 2], [0, 2]]
Output: 2

Explanation: the two tuples are:

  1. (0, 0, 0, 1): 1 + (-2) + (-1) + 2 = 0
  2. (1, 1, 0, 0): 2 + (-1) + (-1) + 0 = 0

Example 2:

Input: k = 4, arrays = [[0], [0], [0], [0]]
Output: 1

Example 3:

Input: k = 3, arrays = [[1, -1], [1, -1], [1, -1]]
Output: 0

Explanation: every sum is an odd count of +1 and -1, so it is odd and never 0.

Constraints:

  • 1 <= k <= 6
  • 1 <= n <= 30
  • -228 <= arrays[i][j] <= 228
  • The answer fits in a 64-bit integer.
class Solution:
def fourSumCount(
self, nums1: list[int], nums2: list[int], nums3: list[int], nums4: list[int]
) -> int:
def kSum(arrays):
def getCounter(arrays):
counter = collections.Counter()
for combo in itertools.product(*arrays):
counter[sum(combo)] += 1
return counter
 
half = len(arrays) // 2
left, right = getCounter(arrays[:half]), getCounter(arrays[half:])
ans = 0
for total, count in left.items():
ans += count * right[-total]
return ans
 
return kSum([nums1, nums2, nums3, nums4])
top ↑

Numbers arrive one at a time​

Otto, your interviewer

170. Two Sum III - Data structure design​

170

Two Sum III - Data structure design

LeetCode
Easy

Design a data structure that accepts a stream of integers and checks if it has a pair of integers that sum up to a particular value. Implement the TwoSum class: TwoSum() initializes with an empty array, void add(int number) adds number to the data structure, and boolean find(int value) returns true if there exists any pair of numbers whose sum is equal to value, otherwise false.

  • ["TwoSum", "add", "add", "add", "find", "find"] with [[], [1], [3], [5], [4], [7]] returns [null, null, null, null, true, false]: 1 + 3 = 4, and no pair sums to 7.
  • Constraints: -105 <= number <= 105, -231 <= value <= 231 - 1, at most 104 calls in total to add and find.
FIG. 170 TWO SUM III SET OF SUMS● INTERACTIVE
visualization loads as you reach it
class TwoSum:
def __init__(self):
self.arr = []
self.totals = set()
 
def add(self, number: int) -> None:
for num in self.arr:
self.totals.add(num + number)
self.arr.append(number)
 
def find(self, value: int) -> bool:
return value in self.totals

Every approach

DesignaddfindSpace
Set of SumsO(n)O(1)O(n + min(n(n-1)/2, R))
Count MapO(1)O(d)O(d)
Sorted List + 2 Converging PointersO(log n + n)O(n)O(n)

Which one depends on whether add or find calls dominate.

top ↑

The input is a BST​

Otto, your interviewer
ApproachAux spaceCost
Hash set over any traversal (Two Sum's map)O(n + h)ignores the BST order
Search the tree for each node's complementO(2h)O(n·h) time
Inorder into a list, then 2 Converging PointersO(n + h)rebuilds the array that 167 started with
Min and max iteratorsO(h)the expected answer
Narrative

Min and max iterators run 167's two pointers on the tree itself. The min iterator is a stack-based inorder walk: push the left spine, and after each pop push the left spine of the popped node's right child. Each next() returns the next-smallest value. The max iterator is its mirror (right spine, then the left child's right spine), returning the next-largest value. Sum too low: advance min. Sum too high: advance max. Stop when both return the same node. Each stack holds one root-to-leaf path, so it costs O(h) instead of the list's O(n).

653. Two Sum IV - Input is a BST​

653

Two Sum IV - Input is a BST

LeetCode
Easy

DFS (Iterative, BST search per node)

FIG. TWO SUM IV BST SEARCH● INTERACTIVE
visualization loads as you reach it
class Solution:
def findTarget(self, root: Optional[TreeNode], k: int) -> bool:
def search(head, node):
if not head:
return
total = head.val + node.val
if total == k and head != node:
return True
elif total < k:
return search(head.right, node)
else:
return search(head.left, node)
 
stack = [root]
while stack:
node = stack.pop()
if node:
if search(root, node):
return True
stack.append(node.right)
stack.append(node.left)
return False
top ↑

The constraint matrix​

ProblemInputAnswer shapeBest timeAux space
1 Two Sumthe hubUnsortedOne pair of indicesO(N)O(N)the index map
167 Two Sum IIfrom 1SortedOne pair of indicesO(N)O(1)the sorting is the map
2824 Count Pairs Under Targetfrom 167UnsortedA count of pairsO(N log N)O(1)beyond the sort
1099 Two Sum Less Than Kfrom 167UnsortedThe largest sum under kO(N log N)O(1)beyond the sort
2563 Count the Number of Fair Pairsfrom 2824UnsortedA count of pairs in a rangeO(N log N)O(1)beyond the sort
15 3Sumfrom 167UnsortedAll unique triplesO(N2)O(1)beyond the sort
259 3Sum Smallerfrom 15UnsortedA count of triplesO(N2)O(1)beyond the sort
611 Valid Triangle Numberfrom 15UnsortedA count of triplesO(N2)O(1)beyond the sort
16 3Sum Closestfrom 15UnsortedThe nearest sumO(N2)O(1)beyond the sort
923 3Sum With Multiplicityfrom 15UnsortedA count, repeats includedO(N2)O(1)counts over a fixed value range
18 4Sumfrom 15UnsortedAll unique quadruplesO(N3)O(1)beyond the sort
454 4Sum IIfrom 18Four separate arraysA count, not the tuplesO(N2)O(N2)the map comes back
kSum kSum Countfrom 454k separate arraysA countO(Nk/2)O(Nk/2)the map comes back
170 Two Sum IIIfrom 1A stream of addsYes or no, per queryO(1) addor O(1) find, not bothO(d)the count map
653 Two Sum IVfrom 167A BST, no indicesYes or noO(N)O(h)two stack iterators

When the answer changes from one pair to every distinct tuple, early return is gone and deduping becomes the work. When it shrinks to a count over separate arrays, the map returns.