Facebook Pixel

454. 4Sum II

MediumArrayHash Table
Leetcode Link

Problem Description

You are given four integer arrays nums1, nums2, nums3, and nums4, where each array has the same length n. Your task is to find the number of valid tuples (i, j, k, l) where each index satisfies 0 <= i, j, k, l < n, and the sum of elements at these indices equals zero: nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0.

In other words, you need to pick one element from each of the four arrays such that their sum equals zero, and count how many different ways you can do this. The indices i, j, k, and l can be any valid indices within their respective arrays and don't need to be the same.

For example, if you have four arrays each containing some integers, you want to count all possible combinations where selecting one number from each array results in a sum of zero. Each combination is defined by the tuple of indices (i, j, k, l) used to select the elements.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The naive approach would be to check all possible combinations using four nested loops, which would give us O(n^4) time complexity. This becomes inefficient for large values of n.

To optimize this, we can split the problem into two parts. Instead of looking at all four arrays together, we can group them into pairs: (nums1, nums2) and (nums3, nums4).

The key insight is that if nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0, then we can rearrange this equation as: nums1[i] + nums2[j] == -(nums3[k] + nums4[l])

This means the sum from the first pair must be the negative of the sum from the second pair.

By precomputing all possible sums from nums1 and nums2 and storing them in a hash table with their frequencies, we can then iterate through all possible sums from nums3 and nums4. For each sum c + d from the second pair, we check if -(c + d) exists in our hash table. If it does, we add its frequency to our answer.

This reduces the time complexity from O(n^4) to O(n^2) because we only need two nested loops for each pair. The hash table allows us to perform the lookup in O(1) time, making the overall solution much more efficient.

Solution Approach

The solution uses a hash table to efficiently count the valid tuples. Here's how the implementation works:

Step 1: Build the Hash Table

First, we create a hash table cnt using Python's Counter to store all possible sums from nums1 and nums2:

cnt = Counter(a + b for a in nums1 for b in nums2)

This line iterates through every element a in nums1 and every element b in nums2, calculating a + b for all pairs. The Counter automatically tracks how many times each sum appears. For example, if the sum 5 can be formed in 3 different ways, then cnt[5] = 3.

Step 2: Count Valid Tuples

Next, we iterate through all pairs from nums3 and nums4:

return sum(cnt[-(c + d)] for c in nums3 for d in nums4)

For each element c in nums3 and each element d in nums4, we calculate their sum c + d. Since we need the total sum of all four elements to be zero, we look for -(c + d) in our hash table.

If -(c + d) exists in cnt, it means there are cnt[-(c + d)] ways to form this negative sum using elements from nums1 and nums2. Each of these ways, combined with the current c and d, forms a valid tuple that sums to zero.

The sum() function accumulates all these counts to give us the total number of valid tuples.

Time and Space Complexity:

  • Time: O(n²) - We iterate through all pairs twice (once for building the hash table, once for counting)
  • Space: O(n²) - The hash table can store up to different sums in the worst case

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a small example to illustrate the solution approach.

Given arrays:

  • nums1 = [1, 2]
  • nums2 = [-2, -1]
  • nums3 = [-1, 2]
  • nums4 = [0, 2]

Step 1: Build the hash table from nums1 and nums2

We calculate all possible sums from nums1 and nums2:

  • nums1[0] + nums2[0] = 1 + (-2) = -1
  • nums1[0] + nums2[1] = 1 + (-1) = 0
  • nums1[1] + nums2[0] = 2 + (-2) = 0
  • nums1[1] + nums2[1] = 2 + (-1) = 1

Our hash table cnt becomes:

cnt = {-1: 1, 0: 2, 1: 1}

This means the sum -1 appears once, 0 appears twice, and 1 appears once.

Step 2: Count valid tuples using nums3 and nums4

Now we iterate through all pairs from nums3 and nums4, looking for the negative of their sum in our hash table:

  • nums3[0] + nums4[0] = -1 + 0 = -1
    • Look for -(-1) = 1 in cnt → cnt[1] = 1 → Add 1 to result
  • nums3[0] + nums4[1] = -1 + 2 = 1
    • Look for -(1) = -1 in cnt → cnt[-1] = 1 → Add 1 to result
  • nums3[1] + nums4[0] = 2 + 0 = 2
    • Look for -(2) = -2 in cnt → Not found → Add 0 to result
  • nums3[1] + nums4[1] = 2 + 2 = 4
    • Look for -(4) = -4 in cnt → Not found → Add 0 to result

Total count = 1 + 1 + 0 + 0 = 2

Let's verify the two valid tuples:

  1. (i=0, j=0, k=0, l=1): 1 + (-2) + (-1) + 2 = 0
  2. (i=0, j=1, k=0, l=0): 1 + (-1) + (-1) + 0 = 0

Actually, let me recalculate more carefully:

For nums3[0] + nums4[0] = -1, we need cnt[1] = 1, which corresponds to nums1[1] + nums2[1] = 1. This gives us the tuple (1, 1, 0, 0): 2 + (-1) + (-1) + 0 = 0

For nums3[0] + nums4[1] = 1, we need cnt[-1] = 1, which corresponds to nums1[0] + nums2[0] = -1. This gives us the tuple (0, 0, 0, 1): 1 + (-2) + (-1) + 2 = 0

The algorithm efficiently found both valid combinations without checking all 16 possible tuples!

Solution Implementation

1from typing import List
2from collections import Counter
3
4class Solution:
5    def fourSumCount(
6        self, nums1: List[int], nums2: List[int], nums3: List[int], nums4: List[int]
7    ) -> int:
8        # Create a hash map to store the frequency of all possible sums from nums1 and nums2
9        # Key: sum of a pair (a, b) where a is from nums1 and b is from nums2
10        # Value: frequency of this sum
11        sum_count = Counter(a + b for a in nums1 for b in nums2)
12      
13        # For each pair (c, d) from nums3 and nums4, check if -(c + d) exists in the hash map
14        # If it exists, add its frequency to the result
15        # This works because we need a + b + c + d = 0, which means a + b = -(c + d)
16        result = sum(sum_count[-(c + d)] for c in nums3 for d in nums4)
17      
18        return result
19
1class Solution {
2    public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
3        // HashMap to store the sum of pairs from nums1 and nums2 as key,
4        // and their frequency as value
5        Map<Integer, Integer> sumFrequencyMap = new HashMap<>();
6      
7        // Calculate all possible sums of pairs from nums1 and nums2
8        // Store each sum and its frequency in the map
9        for (int elementA : nums1) {
10            for (int elementB : nums2) {
11                int sum = elementA + elementB;
12                // Increment the frequency of this sum
13                // If sum doesn't exist, initialize with 1; otherwise, add 1 to existing count
14                sumFrequencyMap.merge(sum, 1, Integer::sum);
15            }
16        }
17      
18        // Counter for valid four-sum combinations that equal zero
19        int totalCount = 0;
20      
21        // For each pair from nums3 and nums4, check if the negation of their sum
22        // exists in our map (which would make the total sum zero)
23        for (int elementC : nums3) {
24            for (int elementD : nums4) {
25                int currentSum = elementC + elementD;
26                int targetSum = -currentSum;  // Need this value from first two arrays to make total zero
27              
28                // Add the frequency of the target sum to our result
29                // If targetSum doesn't exist in map, add 0
30                totalCount += sumFrequencyMap.getOrDefault(targetSum, 0);
31            }
32        }
33      
34        return totalCount;
35    }
36}
37
1class Solution {
2public:
3    int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
4        // Hash map to store the frequency of sums from nums1 and nums2
5        unordered_map<int, int> sumFrequency;
6      
7        // Calculate all possible sums of pairs from nums1 and nums2
8        // Store the frequency of each sum in the hash map
9        for (int valueFromNums1 : nums1) {
10            for (int valueFromNums2 : nums2) {
11                int currentSum = valueFromNums1 + valueFromNums2;
12                ++sumFrequency[currentSum];
13            }
14        }
15      
16        // Counter for valid quadruplets that sum to zero
17        int totalCount = 0;
18      
19        // For each pair from nums3 and nums4, check if the negative of their sum
20        // exists in the hash map (which would make the total sum equal to zero)
21        for (int valueFromNums3 : nums3) {
22            for (int valueFromNums4 : nums4) {
23                int currentSum = valueFromNums3 + valueFromNums4;
24                int targetSum = -currentSum;
25              
26                // Add the frequency of the target sum to the total count
27                // If targetSum doesn't exist in map, it returns 0 by default
28                totalCount += sumFrequency[targetSum];
29            }
30        }
31      
32        return totalCount;
33    }
34};
35
1/**
2 * Counts the number of tuples (i, j, k, l) such that nums1[i] + nums2[j] + nums3[k] + nums4[l] = 0
3 * @param nums1 - First array of integers
4 * @param nums2 - Second array of integers
5 * @param nums3 - Third array of integers
6 * @param nums4 - Fourth array of integers
7 * @returns The count of tuples that sum to zero
8 */
9function fourSumCount(nums1: number[], nums2: number[], nums3: number[], nums4: number[]): number {
10    // HashMap to store the frequency of sums from nums1 and nums2
11    const sumFrequencyMap: Record<number, number> = {};
12  
13    // Calculate all possible sums of pairs from nums1 and nums2
14    // Store their frequencies in the map
15    for (const valueFromNums1 of nums1) {
16        for (const valueFromNums2 of nums2) {
17            const pairSum: number = valueFromNums1 + valueFromNums2;
18            sumFrequencyMap[pairSum] = (sumFrequencyMap[pairSum] || 0) + 1;
19        }
20    }
21  
22    // Count the number of valid four-element tuples
23    let totalCount: number = 0;
24  
25    // For each pair from nums3 and nums4, check if the negative of their sum
26    // exists in our frequency map (which would make the total sum zero)
27    for (const valueFromNums3 of nums3) {
28        for (const valueFromNums4 of nums4) {
29            const pairSum: number = valueFromNums3 + valueFromNums4;
30            const targetSum: number = -pairSum;
31            // Add the frequency of the complement sum to our total count
32            totalCount += sumFrequencyMap[targetSum] || 0;
33        }
34    }
35  
36    return totalCount;
37}
38

Time and Space Complexity

Time Complexity: O(n²)

The algorithm consists of two main parts:

  • First, we generate all possible sums from nums1 and nums2 using nested loops (a + b for a in nums1 for b in nums2), which takes O(n²) time where n is the length of each array.
  • Then, we iterate through all possible sums from nums3 and nums4 (for c in nums3 for d in nums4), which also takes O(n²) time. For each sum c + d, we perform a dictionary lookup cnt[-(c + d)] which is O(1) on average.
  • Total time complexity: O(n²) + O(n²) = O(n²)

Space Complexity: O(n²)

The Counter object cnt stores all possible sums from nums1 and nums2. Since there are n elements in nums1 and n elements in nums2, there can be at most different sums (in the worst case where all sums are unique). Therefore, the space complexity is O(n²).

Common Pitfalls

1. Using Regular Dictionary Instead of Counter

A common mistake is attempting to manually build a dictionary to count sums, which can lead to KeyError when looking up non-existent keys:

Incorrect approach:

sum_count = {}
for a in nums1:
    for b in nums2:
        sum_ab = a + b
        sum_count[sum_ab] = sum_count.get(sum_ab, 0) + 1

result = 0
for c in nums3:
    for d in nums4:
        target = -(c + d)
        if target in sum_count:  # Must check existence first
            result += sum_count[target]

Why Counter is better:

  • Counter automatically returns 0 for missing keys, eliminating the need for existence checks
  • The code becomes cleaner and less error-prone
  • With Counter: sum_count[-(c + d)] safely returns 0 if the key doesn't exist

2. Attempting O(n⁴) Brute Force Solution

Beginners might try four nested loops to check all possible combinations:

Inefficient approach:

count = 0
for i in range(len(nums1)):
    for j in range(len(nums2)):
        for k in range(len(nums3)):
            for l in range(len(nums4)):
                if nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0:
                    count += 1

This results in O(n⁴) time complexity, which becomes impractical for larger inputs (e.g., n=200 means 1.6 billion operations).

3. Incorrect Grouping of Arrays

Some might try to optimize by grouping arrays incorrectly, such as (nums1 + nums3) and (nums2 + nums4):

# Wrong grouping - doesn't reduce complexity effectively
sum_count = Counter(a + c for a in nums1 for c in nums3)
result = sum(sum_count[-(b + d)] for b in nums2 for d in nums4)

While this still works, it doesn't provide any advantage. The key insight is that any 2+2 split works equally well, and the standard approach groups adjacent arrays for clarity.

4. Memory Optimization Confusion

Trying to "optimize" by using a set instead of Counter to save space:

# Incorrect - loses frequency information
sum_set = set(a + b for a in nums1 for b in nums2)
# This only tells us if a sum exists, not how many times

The frequency information is crucial - if a sum appears multiple times, each occurrence represents a different valid tuple combination.

Solution: Always use Counter or a proper frequency map to maintain count information for each sum.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Given a sorted array of integers and an integer called target, find the element that equals to the target and return its index. Select the correct code that fills the ___ in the given code snippet.

1def binary_search(arr, target):
2    left, right = 0, len(arr) - 1
3    while left ___ right:
4        mid = (left + right) // 2
5        if arr[mid] == target:
6            return mid
7        if arr[mid] < target:
8            ___ = mid + 1
9        else:
10            ___ = mid - 1
11    return -1
12
1public static int binarySearch(int[] arr, int target) {
2    int left = 0;
3    int right = arr.length - 1;
4
5    while (left ___ right) {
6        int mid = left + (right - left) / 2;
7        if (arr[mid] == target) return mid;
8        if (arr[mid] < target) {
9            ___ = mid + 1;
10        } else {
11            ___ = mid - 1;
12        }
13    }
14    return -1;
15}
16
1function binarySearch(arr, target) {
2    let left = 0;
3    let right = arr.length - 1;
4
5    while (left ___ right) {
6        let mid = left + Math.trunc((right - left) / 2);
7        if (arr[mid] == target) return mid;
8        if (arr[mid] < target) {
9            ___ = mid + 1;
10        } else {
11            ___ = mid - 1;
12        }
13    }
14    return -1;
15}
16

Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More