LeetCode #18 — Medium

4Sum

A complete visual walkthrough of the sorted two-pointer approach — generalizing 3Sum with an extra layer of iteration.

Solve on LeetCode
The Problem

Problem Statement

Given an array nums of n integers, return an array of all the unique quadruplets [nums[a], nums[b], nums[c], nums[d]] such that:

  • 0 <= a, b, c, d < n
  • a, b, c, and d are distinct.
  • nums[a] + nums[b] + nums[c] + nums[d] == target

You may return the answer in any order.

Example 1:

Input: nums = [1,0,-1,0,-2,2], target = 0
Output: [[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]

Example 2:

Input: nums = [2,2,2,2,2], target = 8
Output: [[2,2,2,2]]

Constraints:

  • 1 <= nums.length <= 200
  • -109 <= nums[i] <= 109
  • -109 <= target <= 109
Patterns Used

Roadmap

  1. Brute Force — Four Nested Loops
  2. The Core Insight — Generalizing 3Sum
  3. Algorithm Walkthrough
  4. Edge Cases
  5. Full Annotated Java Code
  6. Interactive Demo
  7. Complexity Analysis
Step 01

Brute Force — Four Nested Loops

The most straightforward approach: try every combination of four distinct indices and check if they sum to the target.

Brute Force Pseudocode

for a in 0..n
  for b in a+1..n
    for c in b+1..n
      for d in c+1..n
        if nums[a]+nums[b]+nums[c]+nums[d] == target
          add([nums[a], nums[b], nums[c], nums[d]])

This runs in O(n4) time. For n = 200 that is 2004 = 1.6 billion operations. We also need a set to avoid duplicate quadruplets, making it even slower.

💡

Can we do better? If you have solved 2Sum and 3Sum, the pattern is clear: sort the array, fix the outer elements with loops, and use two pointers for the innermost pair. Each level of "fixing" removes one power from the exponent.

Step 02

The Core Insight — Generalizing 3Sum

The key idea is a layered reduction. Just as 3Sum reduced to 2Sum by fixing one element, 4Sum reduces to 3Sum by fixing one more. After sorting, we can skip duplicates at every level.

The Layered Structure

Loop i Fix first element. Skip if nums[i] == nums[i-1]. O(n)
Loop j Fix second element (j > i). Skip if nums[j] == nums[j-1]. O(n)
Two pointers left starts at j+1, right starts at end. Move inward. O(n)
Total O(n) × O(n) × O(n) from the three levels combined. O(n³)

Why sorting is essential: Sorting enables two things at once. First, the two-pointer technique works because the array is monotonic. Second, duplicate skipping becomes trivial — identical adjacent values can be skipped with a simple comparison. Sorting costs O(n log n), which is dominated by O(n³).

Duplicate Skipping at Each Level

Each level skips over values that are the same as the previous iteration to avoid generating the same quadruplet more than once.

Level i
if (i > 0 && nums[i] == nums[i-1]) continue;
Level j
if (j > i+1 && nums[j] == nums[j-1]) continue;
Level left/right
while (left < right && nums[left] == nums[left+1]) left++;
while (left < right && nums[right] == nums[right-1]) right--;
Step 03

Algorithm Walkthrough

Let's walk through nums = [1, 0, -1, 0, -2, 2] with target = 0. First, sort the array:

Sorted Array

-2
-1
0
0
1
2
0
1
2
3
4
5

Finding Quadruplet: [-2, -1, 1, 2]

With i=0 (value -2) and j=1 (value -1), we need left+right to sum to 3. Two pointers scan from both ends.

-2i
-1j
0
0
1left
2right
sum = -2 + -1 + 1 + 2 = 0 = target

Finding Quadruplet: [-2, 0, 0, 2]

With i=0 (value -2) and j=2 (value 0), we need left+right to sum to 2.

-2i
-1
0j
0left
1
2right
sum = -2 + 0 + 0 + 2 = 0 = target

Finding Quadruplet: [-1, 0, 0, 1]

With i=1 (value -1) and j=2 (value 0), we need left+right to sum to 1.

-2
-1i
0j
0left
1right
2
sum = -1 + 0 + 0 + 1 = 0 = target
🎯

Result: 3 unique quadruplets found. Notice how duplicate skipping ensures we never process the same combination twice, even though the value 0 appears at two different indices.

Step 04

Edge Cases

Several tricky scenarios can trip up a naive implementation.

  • ⚠️ Fewer than 4 elements. If nums.length < 4, there are no quadruplets to form. Return an empty list immediately.
  • ⚠️ Integer overflow. Summing four integers can exceed the range of int (e.g., nums = [109, 109, 109, 109]). Cast to long before adding: (long)nums[i] + nums[j] + nums[left] + nums[right].
  • ⚠️ Many duplicates. An array like [0, 0, 0, 0, 0, 0] with target 0 should produce exactly one quadruplet [0, 0, 0, 0]. Without proper duplicate skipping, you would get many copies.
  • ⚠️ All zeros. Similar to above, but worth testing explicitly. The algorithm should handle it cleanly via the skip logic at all three levels.
  • ⚠️ No valid quadruplet. The algorithm naturally returns an empty list. No special handling needed, but ensure your code does not crash on edge inputs like nums = [1,2,3,4], target = 100.
Step 05

Full Annotated Java Code

class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {
        Arrays.sort(nums);                // Sort to enable two-pointer + dedup
        List<List<Integer>> result = new ArrayList<>();

        // First element: index i
        for (int i = 0; i < nums.length - 3; i++) {
            // Skip duplicate values for i
            if (i > 0 && nums[i] == nums[i - 1]) continue;

            // Second element: index j
            for (int j = i + 1; j < nums.length - 2; j++) {
                // Skip duplicate values for j
                if (j > i + 1 && nums[j] == nums[j - 1]) continue;

                // Two pointers for the remaining pair
                int left = j + 1, right = nums.length - 1;

                while (left < right) {
                    // Use long to prevent integer overflow!
                    long sum = (long) nums[i] + nums[j]
                              + nums[left] + nums[right];

                    if (sum == target) {
                        // Found a valid quadruplet
                        result.add(Arrays.asList(
                            nums[i], nums[j],
                            nums[left], nums[right]
                        ));

                        // Skip duplicates for left pointer
                        while (left < right
                               && nums[left] == nums[left + 1])
                            left++;
                        // Skip duplicates for right pointer
                        while (left < right
                               && nums[right] == nums[right - 1])
                            right--;

                        // Move both inward past the match
                        left++;
                        right--;
                    } else if (sum < target) {
                        left++;   // Need a larger sum
                    } else {
                        right--;  // Need a smaller sum
                    }
                }
            }
        }
        return result;
    }
}
📝

The long cast is critical. Without it, nums[i] + nums[j] + nums[left] + nums[right] can overflow a 32-bit integer. Casting the first operand to long promotes the entire expression to 64-bit arithmetic.

Step 06

Interactive Demo

Enter an array and target, then step through the algorithm to see how i, j, left, and right move through the sorted array.

⚙ 4Sum Visualizer



Step 07

Complexity Analysis

Time
O(n3)
Space
O(log n)

The outer two loops contribute O(n2), and the inner two-pointer scan is O(n), giving O(n3) total. Sorting is O(n log n), dominated by the cubic term. Space is O(1) excluding the output list — the sort is in-place and we only use pointer variables.

Approach Breakdown

BRUTE FORCE
O(n4) time
O(1) space

Four nested loops try every combination of four distinct indices. Each loop iterates up to n times, giving n4 total comparisons. Only loop variables and the result list are used, so auxiliary space is O(1).

HASH MAP
O(n3) time
O(n) space

Fix two elements with O(n2) nested loops, then use a hash map for O(1) lookups to find the fourth element. The hash map stores up to n values, giving O(n) space. Duplicate handling adds complexity compared to the two-pointer approach.

TWO POINTER
O(n3) time
O(1) space

Two outer loops fix two elements in O(n2), then two pointers converge from both ends of the remaining subarray in O(n). Sorting costs O(n log n), dominated by the O(n3) main phase. In-place sort and pointer variables keep space at O(1).

🎯

Each additional "k" in k-Sum adds one O(n) loop. 2Sum = O(n), 3Sum = O(n2), 4Sum = O(n3). The pattern is: sort the array, fix k-2 elements with nested loops, then use two pointers for the last pair. The two-pointer technique always shaves one power of n from the brute force.

Coach Notes

Common Mistakes

Review these before coding to avoid predictable interview regressions.

Moving both pointers on every comparison

Wrong move: Advancing both pointers shrinks the search space too aggressively and skips candidates.

Usually fails on: A valid pair can be skipped when only one side should move.

Fix: Move exactly one pointer per decision branch based on invariant.