Two Sum
Given an array of integers nums and an integer target, return indices of the two numbers such that they add up to target. You may assume that each input would have exactly one solution, and you may not use the same element twice. You can return the answer in any order.
- 2 <= nums.length <= 10⁴
- -10⁹ <= nums[i] <= 10⁹
- -10⁹ <= target <= 10⁹
- Exactly one valid answer exists
nums = [2,7,11,15], target = 9[0,1]nums = [3,2,4], target = 6[1,2]nums = [3,3], target = 6[0,1]Two Sum hands us a list of numbers and a target, and asks which two different positions hold values that add up to the target. The word positions matters: we must return the two indices, not the values, and we may not use the same position twice. The problem also promises that exactly one valid pair exists — a guarantee that frees us from worrying about ties, or about what to return when nothing matches.
With no better idea yet, we can test every possible pair: hold the first number and try it against each number after it, then hold the second number and try it against each number after that one, and so on until some pair hits the target.
for i in range(len(nums)):
for j in range(i + 1, len(nums)): # j only looks at positions AFTER i
if nums[i] + nums[j] == target:
return [i, j]One line deserves a pause: the inner loop starts at i + 1, not at 0. That single choice does two jobs. It guarantees j is always a different position than i, satisfying the "don't use the same element twice" rule. And it avoids re-testing pairs in reverse — the numbers at positions (1, 4) add up to the same total as (4, 1), so checking both orders would be pure waste.
Now the cost. The first number gets compared against up to N - 1 partners, the second against N - 2, and so on — roughly N²/2 pair-checks in total for an array of N numbers. Notice what that means: double the array, and the work grows four times, because both loops doubled. This scaling is written O(N²), "quadratic time". At this problem's maximum size, N = 10,000, that is on the order of fifty million checks for a single query. The approach is correct — but it repeats an enormous amount of searching. Where exactly is the waste?
Stand on any number x and look at what you actually need. If the target is 9 and x is 2, the only number in the world that can complete the pair is 9 - 2 = 7. Not "some suitable partner" — exactly 7. That required partner, target - x, is called the complement. This reframe changes the problem completely: instead of vaguely searching among roughly N²/2 pairs, we walk the numbers one by one, and at each step search for a single value we can name in advance. The brute force's inner loop was doing precisely that search — by scanning up to N elements every time. Scanning is only necessary when you don't remember what you have already seen. So: what if we remembered?
A hash map (Python calls it a dictionary) stores entries of the form key → value, and it has the one property this solution depends on: asking "do you contain this key, and what is stored under it?" takes roughly the same, tiny, constant amount of time whether the map holds five entries or five million. The lookup cost does not grow with the map's size. (The trick underneath: from the key itself, the map computes where that key's entry must live, and jumps straight there instead of scanning.) We will use the numbers we have already visited as keys, and the index where we saw each one as its value.
The plan, then: walk the array once. At each number x, first ask the map whether the complement target - x has been seen before. If yes, the stored index and the current index are the answer. If no, record x with its index and step forward.
seen = {} # value -> index where we saw it
for i, x in enumerate(nums): # i is the position, x the value stored there
partner = target - x
if partner in seen: # a constant-time question
return [seen[partner], i]
seen[x] = i # record x only AFTER the check aboveThe single pass makes N stops, and each stop performs one constant-time lookup and at most one constant-time insert, so the total time falls from O(N²) to O(N). The price is memory: in the worst case the map ends up remembering nearly all N numbers, so we now spend O(N) extra space where the brute force needed none. This exchange — spending memory to avoid repeated scanning — is one of the most reusable ideas in all of algorithms. Train the reflex now: whenever you catch an inner loop whose only job is "find one specific value I could name before searching", ask whether a hash map could have remembered it. That one question dissolves a whole family of interview problems: pair sums, "have I seen this before?" checks, frequency counting. And one closing thought worth saying aloud in an interview: everything above assumed the array is unsorted. If it were sorted, a different technique that needs no extra memory becomes possible — that is exactly the follow-up problem, Two Sum II.
One-Pass Hash Map
Checking for complement while iterating
Key Insight
We can iterate through the array once. For each element, we calculate the complement (Target - Current). If this complement is already in our hash map, we found the pair!
Time & Space
Time: O(N) because we traverse once. Space: O(N) because the hash map stores up to N elements.