186 lines
8.1 KiB
YAML
186 lines
8.1 KiB
YAML
title: 4Sum II
|
|
slug: 4sum-ii
|
|
difficulty: medium
|
|
leetcode_id: 454
|
|
leetcode_url: https://leetcode.com/problems/4sum-ii/
|
|
categories:
|
|
- arrays
|
|
- hash-tables
|
|
patterns:
|
|
- slug: hashing
|
|
is_optimal: true
|
|
|
|
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
|
|
|
|
|
|
|
|
**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
|
|
|
|
|
|
|
|
**Step 3: Return the result**
|
|
|
|
- Return `count` as the total number of tuples that sum to zero
|
|
|
|
|
|
|
|
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.
|