LeetCode #1679 Medium

Max Number of K-Sum Pairs

Max Number of K-Sum Pairs: repeatedly remove two numbers summing to k and return the maximum number of such operations you can perform.

Constraints
  • 1 <= nums.length <= 10⁵
  • 1 <= nums[i] <= 10⁹
  • 1 <= k <= 10⁹
arrayhash-tabletwo-pointerssorting
Open on LeetCode ↗
02

Intuition

Max number of k sum pairs counts how many pairs summing to k can be removed from an array, where each element is used at most once. The removal constraint is what separates this from ordinary pair counting. Two approaches work, and they differ in what they exploit. The hash map version scans once, keeping counts of values seen so far. For each element, if its complement k − num is available, pair them off and decrement the complement's count; otherwise record the current value for a later match. Decrementing is what enforces single use — without it, one element pairs with many partners and the count is inflated. The two-pointer version sorts first, then converges from both ends: - If the two ends sum to k, count a pair and move both inwards; if the sum is too small move left inwards, and if too large move right inwards. Moving both pointers on a match is the detail that enforces the constraint — advancing only one leaves an already-used element in play. The sorted order makes the direction decision unambiguous: a sum below k can only be raised by taking a larger left value, and a sum above k only lowered by taking a smaller right value. The hash map is O(n) time and O(n) space; sorting makes the two-pointer version O(n log n) time and O(1) space. Neither dominates — the choice depends on whether time or space is tighter. Duplicates are handled naturally by both, since counts and positions are tracked rather than distinct values.

How to spot this pattern

'Find pairs summing to a target' is the Two Sum family, and the counting-map variant appears whenever elements can be consumed. The tell here is repeatedly remove — meaning disjoint pairs — which rules out reusing an element and makes a count map, not a plain set, the right structure.

03

Approach

Try it first

Before reading on: since every operation scores exactly 1, does it ever matter which pair you form first? Then decide what you must remember about numbers that have not yet found a partner. Aim for O(n).

1

Note the single-use constraint

Each element can join at most one pair, which is what distinguishes this from plain pair counting. Both approaches must enforce it explicitly.

2

Count complements in a map

Scanning once, check whether k - num is available. If so, pair them; otherwise record the current value for a later match.

3

Decrement after pairing

Reduce the complement's count when a pair is formed. Without this, one element pairs with many partners and the total is inflated.

4

Or sort and converge

Sort, then place pointers at both ends. The sorted order makes the direction unambiguous — a small sum needs a larger left value, a large sum a smaller right one.

5

Move both pointers on a match

When the ends sum to k, count the pair and advance both pointers. Moving only one leaves an already-used element available.

6

Cost of each approach

The map is O(n) time, O(n) space; the two-pointer version is O(n log n) time, O(1) space. Neither dominates — pick by whichever resource is tighter.

04

Solution & live demo

▶1class Solution:
▶2 def maxOperations(self, nums, k):
▶3 waiting = defaultdict(int)
▶4 operations = 0
▶5 for num in nums:
▶6 partner = k - num
▶7 if waiting[partner] > 0:
▶8 waiting[partner] -= 1
▶9 operations += 1
▶10 else:
▶11 waiting[num] += 1
▶12 return operations
05

Common pitfalls

Using a set instead of a count map

✗ Wrong
if partner in seen:
    seen.remove(partner)
✓ Right
if waiting[partner] > 0:
    waiting[partner] -= 1

A set cannot represent two copies of the same value. On [3,3,3,3] with k=6 it forms one pair instead of two, because the second 3 has nowhere to be stored separately.

Counting each pair twice

✗ Wrong
for num in nums:
    if k - num in counts:
        operations += 1
# without consuming either element
✓ Right
waiting[partner] -= 1
operations += 1

Without decrementing, the same waiting number pairs with every later match, inflating the count. Each element may take part in exactly one operation, so the partner must be consumed.

Sorting then scanning linearly for partners

✗ Wrong
nums.sort()
for i in range(len(nums)):
    # inner scan for k - nums[i]
✓ Right
# hash map in one pass, or true two pointers after sorting

Sorting alone does not help unless you actually converge two pointers; a nested scan is O(n²) and the sort adds O(n log n) on top for nothing.

06

Edge cases

No valid pairs, e.g. nums=[1,2], k=10

No partner is ever found, every value simply waits, and the answer is 0.

k is even and a value equals k/2, e.g. [3,3], k=6

The first 3 waits, the second finds it — the count-based map handles self-pairing without special casing.

Duplicates beyond one pair, e.g. [3,3,3,3], k=6

Counts track multiplicity, so two disjoint pairs are formed.

Single element

It waits forever and the answer is 0.

All elements pair up

The answer is n/2, the theoretical maximum.

07

Complexity

Time
O(n)
Space
O(n)
One pass with O(1) map operations. The two-pointer variant is O(n log n) time and O(1) extra space.