title: 4Sum II
slug: 4sum-ii
difficulty: medium
leetcode_id: 454
leetcode_url: https://leetcode.com/problems/4sum-ii/
categories:
  - arrays
  - hash-tables
patterns:
  - two-pointers

function_signature: "def four_sum_count(nums1: list[int], nums2: list[int], nums3: list[int], nums4: list[int]) -> int:"

test_cases:
  visible:
    - input: { nums1: [1, 2], nums2: [-2, -1], nums3: [-1, 2], nums4: [0, 2] }
      expected: 2
    - input: { nums1: [0], nums2: [0], nums3: [0], nums4: [0] }
      expected: 1
  hidden:
    - input: { nums1: [-1, -1], nums2: [-1, 1], nums3: [-1, 1], nums4: [1, -1] }
      expected: 6
    - input: { nums1: [1], nums2: [-1], nums3: [0], nums4: [0] }
      expected: 1
    - input: { nums1: [0, 1, -1], nums2: [-1, 1, 0], nums3: [0, 0, 1], nums4: [-1, 1, 1] }
      expected: 17
    - input: { nums1: [1, 1, 1], nums2: [1, 1, 1], nums3: [-1, -1, -1], nums4: [-1, -1, -1] }
      expected: 81

description: |
  Given four integer arrays `nums1`, `nums2`, `nums3`, and `nums4` all of length `n`, return the number of tuples `(i, j, k, l)` such that:

  - `0 <= i, j, k, l < n`
  - `nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0`

constraints: |
  - `n == nums1.length == nums2.length == nums3.length == nums4.length`
  - `1 <= n <= 200`
  - `-2^28 <= nums1[i], nums2[i], nums3[i], nums4[i] <= 2^28`

examples:
  - input: "nums1 = [1,2], nums2 = [-2,-1], nums3 = [-1,2], nums4 = [0,2]"
    output: "2"
    explanation: "The two tuples are: (0, 0, 0, 1) -> 1 + (-2) + (-1) + 2 = 0, and (1, 1, 0, 0) -> 2 + (-1) + (-1) + 0 = 0."
  - input: "nums1 = [0], nums2 = [0], nums3 = [0], nums4 = [0]"
    output: "1"
    explanation: "The only tuple is (0, 0, 0, 0) -> 0 + 0 + 0 + 0 = 0."

explanation:
  intuition: |
    At first glance, this looks like a problem requiring four nested loops to check every combination — but that would be O(n^4), far too slow.

    The key insight is to **split the problem in half**. Think of it like this: instead of finding four numbers that sum to zero, find two numbers from the first half (arrays 1 and 2) and two numbers from the second half (arrays 3 and 4) that cancel each other out.

    If `a + b + c + d = 0`, then `a + b = -(c + d)`.

    This transforms the problem into a **Two Sum variant**: for every possible sum from the first two arrays, check how many times its negation appears among sums from the last two arrays.

    By precomputing all possible sums from arrays 1 and 2 into a hash map, we can then iterate through arrays 3 and 4 and look up complements in O(1) time. This reduces the complexity from O(n^4) to O(n^2).

  approach: |
    We solve this using a **Hash Map with Split Arrays** approach:

    **Step 1: Build a hash map of sums from the first two arrays**

    - Create an empty hash map `sum_count` to store `{sum: frequency}`
    - Iterate through all pairs `(a, b)` from `nums1` and `nums2`
    - For each pair, calculate `a + b` and increment its count in the hash map

    &nbsp;

    **Step 2: Count complements from the last two arrays**

    - Initialise `count = 0` to track the total number of valid tuples
    - Iterate through all pairs `(c, d)` from `nums3` and `nums4`
    - For each pair, calculate `target = -(c + d)`
    - If `target` exists in `sum_count`, add `sum_count[target]` to our count

    &nbsp;

    **Step 3: Return the result**

    - Return `count` as the total number of tuples that sum to zero

    &nbsp;

    This approach works because each time we find a matching complement, we're counting all valid combinations: if `a + b` appears 3 times and `-(c + d)` matches it, that's 3 valid tuples for this specific `(c, d)` pair.

  common_pitfalls:
    - title: The Brute Force Trap
      description: |
        The naive approach uses four nested loops to check every possible `(i, j, k, l)` combination:

        ```python
        for i in nums1:
            for j in nums2:
                for k in nums3:
                    for l in nums4:
                        if i + j + k + l == 0:
                            count += 1
        ```

        This results in **O(n^4) time complexity**. With `n = 200`, that's 1.6 billion operations — guaranteed TLE.
      wrong_approach: "Four nested loops checking all combinations"
      correct_approach: "Split into two groups and use hash map for O(n^2)"

    - title: Forgetting to Count Duplicates
      description: |
        A common mistake is using a set instead of a counting map for the first two arrays. If the same sum `a + b` can be formed in multiple ways (e.g., `1 + 2` and `0 + 3` both equal 3), each occurrence represents a different valid tuple.

        Using a set would only count one match per sum value, missing valid combinations.
      wrong_approach: "Using a set to store sums from first two arrays"
      correct_approach: "Using a hash map with frequency counts"

    - title: Looking Up Wrong Complement
      description: |
        When searching for complements, ensure you're looking for `-(c + d)`, not `(c + d)`. We need the sum from the first half to be the **negation** of the sum from the second half so they cancel to zero.
      wrong_approach: "Looking up (c + d) in the hash map"
      correct_approach: "Looking up -(c + d) to find values that sum to zero"

  key_takeaways:
    - "**Divide and conquer with hashing**: When dealing with multiple arrays, consider splitting them and using a hash map to bridge the halves"
    - "**Two Sum generalisation**: This is essentially Two Sum where each 'number' is a sum of elements from two arrays"
    - "**Time-space tradeoff**: We use O(n^2) extra space to reduce time from O(n^4) to O(n^2)"
    - "**Count, don't just detect**: When duplicates matter, use a frequency map instead of a set"

  time_complexity: "O(n^2). We iterate through n^2 pairs for the first two arrays to build the map, then another n^2 pairs for the last two arrays to find complements."
  space_complexity: "O(n^2). The hash map can store up to n^2 different sums from the first two arrays."

solutions:
  - approach_name: Hash Map with Split Arrays
    is_optimal: true
    code: |
      from collections import defaultdict

      def four_sum_count(nums1: list[int], nums2: list[int],
                         nums3: list[int], nums4: list[int]) -> int:
          # Store all possible sums from first two arrays with their frequencies
          sum_count = defaultdict(int)

          # Build the hash map: O(n^2)
          for a in nums1:
              for b in nums2:
                  sum_count[a + b] += 1

          # Count complements from last two arrays: O(n^2)
          count = 0
          for c in nums3:
              for d in nums4:
                  # We need a + b + c + d = 0, so a + b = -(c + d)
                  target = -(c + d)
                  # Add the number of ways to form this sum from first two arrays
                  count += sum_count[target]

          return count
    explanation: |
      **Time Complexity:** O(n^2) — Two passes of n^2 iterations each.

      **Space Complexity:** O(n^2) — Hash map stores up to n^2 sums.

      We split the four arrays into two groups. First, we precompute all sums from arrays 1 and 2, storing their frequencies. Then, for each sum from arrays 3 and 4, we look up how many complementary sums exist. The `defaultdict(int)` returns 0 for missing keys, simplifying the lookup.

  - approach_name: Brute Force
    is_optimal: false
    code: |
      def four_sum_count(nums1: list[int], nums2: list[int],
                         nums3: list[int], nums4: list[int]) -> int:
          count = 0

          # Check every possible combination of indices
          for a in nums1:
              for b in nums2:
                  for c in nums3:
                      for d in nums4:
                          if a + b + c + d == 0:
                              count += 1

          return count
    explanation: |
      **Time Complexity:** O(n^4) — Four nested loops.

      **Space Complexity:** O(1) — Only a counter variable.

      This approach checks every possible tuple directly. While correct, it's prohibitively slow for the given constraints (n up to 200). Included to illustrate why the hash map optimisation is essential.
