Two Sum
The Problem
Given an integer array arr and a target integer, find two distinct elements in the array whose sum equals the target. Return them as a pair. If no such pair exists, return an empty array.
It is guaranteed that at most one answer exists.
Input: arr = [2, 8, 3, 6, 4], target = 7
Output: [3, 4]
Input: arr = [2, -1, 5, -4, 3], target = 34
Output: []
Examples
Example 1
Input: arr = [2, 8, 3, 6, 4], target = 7
Output: [3, 4]
Explanation: 3 + 4 = 7
Example 2
Input: arr = [2, -1, 5, -4, 3], target = 34
Output: []
Explanation: No pair sums to 34.
Example 3
Input: arr = [2], target = 2
Output: []
Explanation: Only one element — can't form a pair.
Intuition
The brute-force approach checks every pair: O(n²). The reduction insight is:
Order doesn’t matter → we can sort. Sorted array → two-pointer works.
After sorting, the array has a useful property: arr[left] is the smallest remaining element and arr[right] is the largest. Their sum gives us directional information:
- Sum too small? Only increasing
leftcan raise the sum. - Sum too large? Only decreasing
rightcan lower the sum. - Sum exact? Found it.
---
config:
theme: base
themeVariables:
primaryColor: "#dbeafe"
primaryBorderColor: "#3b82f6"
primaryTextColor: "#1e3a5f"
lineColor: "#64748b"
secondaryColor: "#ede9fe"
tertiaryColor: "#fef9c3"
---
flowchart TB
Sort["Sort arr → [2, 3, 4, 6, 8]"]
Init["left = 0, right = 4"]
Loop{"left < right?"}
Calc["sum = arr[left] + arr[right]"]
Eq{"sum == target?"}
Lt{"sum < target?"}
RetOK(["return [arr[left], arr[right]]"])
MoveL["left++ (need larger sum)"]
MoveR["right-- (need smaller sum)"]
RetNone(["return []"])
Sort --> Init --> Loop
Loop -->|"yes"| Calc --> Eq
Eq -->|"yes"| RetOK
Eq -->|"no"| Lt
Lt -->|"yes"| MoveL --> Loop
Lt -->|"no"| MoveR --> Loop
Loop -->|"no"| RetNone
Two-pointer Two Sum — sort once, then converge from both ends in O(n).
Applying the Diagnostic Questions
| Question | Answer |
|---|---|
| Q1. Does the order of items matter? | No — the problem asks for two values whose sum equals the target; original positions are irrelevant, sorting is permitted |
| Q2. Do we need two items simultaneously? | Yes — we’re evaluating a pair (a, b) where a + b = target at every step |
| Q3. Does traversing from both ends give something special? | Yes — after sorting, arr[left] is always the minimum and arr[right] the maximum of the remaining window; every pointer move has a decisive, guaranteed effect on the sum |
| Q4. Can we reduce further? | No — we’re at the Two Sum base case; the problem is solved directly in one pass |
Q1 — Why “order doesn’t matter, so sorting is permitted”?
Mental model: The output is a pair of values — [3, 4] — not a pair of indices. The problem says nothing about the positions those values came from. A pair (3, 4) summing to 7 is valid whether 3 appeared before or after 4 in the original array. When the answer depends only on values, sorting cannot invalidate a correct answer — and it gives us a structure we can exploit.
Concrete impact: arr = [2, 8, 3, 6, 4], target = 7. Unsorted, the pair (3, 4) sits at non-adjacent indices 2 and 4 — invisible without checking every combination. After sorting to [2, 3, 4, 6, 8], 3 and 4 are adjacent and the min/max structure guides the search directly.
What breaks if position mattered? If the problem asked “find a pair at consecutive positions” or “the second element must appear after the first in the original array,” sorting would destroy the positional information needed. Here there is no such constraint — position-independence is the prerequisite for the sorting reduction.
Q3 — Why “both ends give decisive direction after sorting”?
Mental model: After sorting, arr[left] is the minimum of the unexamined window and arr[right] is the maximum. This gives every pointer move a guaranteed effect: moving left right replaces the minimum with a larger value — the sum strictly increases. Moving right left replaces the maximum with a smaller value — the sum strictly decreases. That decisive direction is what eliminates one element per step with a provable reason.
Concrete trace: sorted [2, 3, 4, 6, 8], target = 7:
left=0 (2), right=4 (8): sum 10 > 7 →arr[right]=8is too large for any remaining left partner (min is 2, so best sum with 8 is already 10) → discardarr[right],right--left=0 (2), right=3 (6): sum 8 > 7 → same reasoning →right--left=0 (2), right=2 (4): sum 6 < 7 →arr[left]=2can never reach target with any remaining right partner (max is 4, best sum 6 < 7) → discardarr[left],left++left=1 (3), right=2 (4): sum 7 == target ✓
What breaks on an unsorted array? Without sorting, moving left right might land on a smaller value — the sum could decrease instead of increase. The decisive direction disappears, and you’d need to try every combination: O(n²).
Solution
from typing import List
class Solution:
def two_sum(self, arr: List[int], target: int) -> List[int]:
# Sort the array to enable the two-pointer reduction
arr.sort()
# Initialize left pointer at the smallest element
left = 0
# Initialize right pointer at the largest element
right = len(arr) - 1
# Run while there are unseen pairs remaining
while left < right:
current = arr[left] + arr[right]
if current == target:
return [arr[left], arr[right]]
elif current < target:
# Sum too small — move left pointer to a larger value
left += 1
else:
# Sum too large — move right pointer to a smaller value
right -= 1
# Pointers crossed — no valid pair exists
return []
# --- Test ---
sol = Solution()
print(sol.two_sum([2, 8, 3, 6, 4], 7)) # [3, 4]
print(sol.two_sum([2, -1, 5, -4, 3], 34)) # []
print(sol.two_sum([2], 2)) # []
print(sol.two_sum([-3, -1, 0, 2, 4, 6], 3)) # [-3, 6] or [1, 2] — sorted pick
Dry Run — Example 1
arr = [2, 8, 3, 6, 4], target = 7
After sort: [2, 3, 4, 6, 8]
| Step | left | right | arr[left] | arr[right] | sum | Action |
|---|---|---|---|---|---|---|
| 1 | 0 | 4 | 2 | 8 | 10 | 10 > 7 → right-- |
| 2 | 0 | 3 | 2 | 6 | 8 | 8 > 7 → right-- |
| 3 | 0 | 2 | 2 | 4 | 6 | 6 < 7 → left++ |
| 4 | 1 | 2 | 3 | 4 | 7 | 7 == 7 → return [3, 4] ✓ |
Complexity Analysis
| Complexity | Reasoning | |
|---|---|---|
| Time | O(n log n) | Dominated by the sort; the two-pointer pass is O(n) |
| Space | O(1) | Sorting in-place; only two pointer variables |
If the array were already sorted, the solution would be O(n) time and O(1) space — the sort is the only overhead.
Edge Cases
| Scenario | Input | Output | Note |
|---|---|---|---|
| Single element | [5], target=5 | [] | Can’t form a pair from one element |
| Two elements, match | [3, 4], target=7 | [3, 4] | One comparison |
| Two elements, no match | [1, 2], target=10 | [] | Loop exits immediately |
| All negatives | [-5, -3, -1], target=-4 | [-3, -1] | Works identically after sort |
| Duplicates | [2, 2, 3], target=4 | [2, 2] | Valid — two distinct positions |
Key Takeaway
Two Sum is the canonical two-pointer reduction problem. The reduction step — sort so that the array becomes directionally meaningful — is the key insight. Once sorted, the two-pointer pass eliminates one element per step with a guaranteed correct reason, giving O(n log n) total instead of O(n²).