1865. Finding Pairs With a Certain Sum
Problem Description
You need to implement a data structure called FindSumPairs
that works with two integer arrays nums1
and nums2
. This data structure should support two types of operations:
-
Add Operation: Add a positive integer value to an element at a specific index in
nums2
. Specifically, when you calladd(index, val)
, it performsnums2[index] += val
. -
Count Operation: Count how many pairs
(i, j)
exist such thatnums1[i] + nums2[j]
equals a given target value. Here,i
can be any valid index innums1
(from0
tonums1.length - 1
) andj
can be any valid index innums2
(from0
tonums2.length - 1
).
The class should have three methods:
FindSumPairs(nums1, nums2)
: Constructor that initializes the object with two integer arraysadd(index, val)
: Addsval
to the element at positionindex
innums2
count(tot)
: Returns the number of pairs where the sum of one element fromnums1
and one element fromnums2
equalstot
For example, if nums1 = [1, 1, 2]
and nums2 = [2, 3, 4]
, and you want to count pairs that sum to 5
, you would look for all combinations where nums1[i] + nums2[j] = 5
. The pairs would be: (1, 4)
, (1, 4)
, and (2, 3)
, giving a count of 3.
The key challenge is handling these operations efficiently, especially since nums2
can be modified through the add
operation, which affects subsequent count
queries.
Intuition
When we need to count pairs that sum to a target value, the naive approach would be to check every possible pair (i, j)
by iterating through both arrays. However, with nums1
having up to 1,000 elements and nums2
having up to 100,000 elements, this would require up to 100 million operations for each count query, which is too slow.
The key observation is the significant size difference between the two arrays. Since nums1
is much smaller (max 1,000 elements) compared to nums2
(max 100,000 elements), we should leverage this asymmetry.
For each element x
in nums1
, we need to find how many elements in nums2
equal tot - x
. Instead of searching through nums2
each time, we can precompute the frequency of each value in nums2
using a hash table. This way, for any value x
from nums1
, we can instantly look up how many times tot - x
appears in nums2
.
The beauty of this approach is that:
- We only iterate through the smaller array (
nums1
) during count operations - We use O(1) hash table lookups instead of O(n) searches in
nums2
- When we modify
nums2
with theadd
operation, we only need to update the frequency count for two values: decrease the count for the old value and increase it for the new value
This transforms what could be an O(n × m) operation into an O(n) operation where n is the size of the smaller array, making the solution much more efficient.
Solution Approach
The solution uses a hash table (Counter in Python) to efficiently track the frequency of elements in nums2
. Here's how each component works:
Initialization (__init__
method):
- Store
nums1
andnums2
as instance variables for later use - Create a Counter object
cnt
that stores the frequency of each element innums2
- For example, if
nums2 = [2, 3, 2, 4]
, thencnt = {2: 2, 3: 1, 4: 1}
Add Operation (add
method):
When adding val
to nums2[index]
, we need to maintain the accuracy of our frequency counter:
- First, decrease the count of the current value:
self.cnt[self.nums2[index]] -= 1
- This removes one occurrence of the old value from our frequency map
- Update the actual value in the array:
self.nums2[index] += val
- Increase the count of the new value:
self.cnt[self.nums2[index]] += 1
- This adds one occurrence of the new value to our frequency map
This three-step process ensures our frequency counter stays synchronized with the actual array values.
Count Operation (count
method):
To count pairs that sum to tot
:
- Iterate through each element
x
innums1
- For each
x
, we need elements fromnums2
that equaltot - x
- Look up
cnt[tot - x]
to get the number of such elements innums2
- Sum all these counts:
sum(self.cnt[tot - x] for x in self.nums1)
For example, if tot = 5
and x = 2
from nums1
, we look for how many times 3
appears in nums2
(since 2 + 3 = 5
).
Time Complexity:
- Initialization: O(m) where m is the length of
nums2
- Add: O(1) - just hash table updates
- Count: O(n) where n is the length of
nums1
Space Complexity:
- O(m) for storing the frequency counter of
nums2
This approach is optimal because it leverages the smaller size of nums1
and uses hash table lookups to avoid repeated iterations through the larger nums2
array.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to illustrate how the solution works.
Initial Setup:
nums1 = [1, 1, 2, 3]
nums2 = [2, 3, 3, 4]
When we initialize FindSumPairs
, we create a frequency counter for nums2
:
cnt = {2: 1, 3: 2, 4: 1}
(value 2 appears once, value 3 appears twice, value 4 appears once)
Operation 1: count(4) We want to find pairs that sum to 4.
- For
nums1[0] = 1
: We need4 - 1 = 3
fromnums2
. Checkcnt[3] = 2
, so we found 2 pairs - For
nums1[1] = 1
: We need4 - 1 = 3
fromnums2
. Checkcnt[3] = 2
, so we found 2 more pairs - For
nums1[2] = 2
: We need4 - 2 = 2
fromnums2
. Checkcnt[2] = 1
, so we found 1 pair - For
nums1[3] = 3
: We need4 - 3 = 1
fromnums2
. Checkcnt[1] = 0
, so we found 0 pairs
Total: 2 + 2 + 1 + 0 = 5
pairs
Operation 2: add(0, 1)
This adds 1 to nums2[0]
, changing it from 2 to 3.
- Step 1: Decrease count of old value:
cnt[2] -= 1
, socnt[2]
becomes 0 - Step 2: Update the array:
nums2[0] = 2 + 1 = 3
, sonums2 = [3, 3, 3, 4]
- Step 3: Increase count of new value:
cnt[3] += 1
, socnt[3]
becomes 3
Now our frequency counter is: cnt = {2: 0, 3: 3, 4: 1}
Operation 3: count(4)
Let's count pairs summing to 4 again with the modified nums2
:
- For
nums1[0] = 1
: We need4 - 1 = 3
fromnums2
. Checkcnt[3] = 3
, so we found 3 pairs - For
nums1[1] = 1
: We need4 - 1 = 3
fromnums2
. Checkcnt[3] = 3
, so we found 3 more pairs - For
nums1[2] = 2
: We need4 - 2 = 2
fromnums2
. Checkcnt[2] = 0
, so we found 0 pairs - For
nums1[3] = 3
: We need4 - 3 = 1
fromnums2
. Checkcnt[1] = 0
, so we found 0 pairs
Total: 3 + 3 + 0 + 0 = 6
pairs
Notice how the frequency counter allows us to instantly know how many times each value appears in nums2
without iterating through it. When we modify nums2
, we only update the relevant counts in O(1) time, and counting pairs only requires iterating through the smaller nums1
array.
Solution Implementation
1from typing import List
2from collections import Counter
3
4
5class FindSumPairs:
6 """
7 A class to find pairs of numbers from two arrays that sum to a target value.
8 Supports updating elements in the second array.
9 """
10
11 def __init__(self, nums1: List[int], nums2: List[int]):
12 """
13 Initialize the data structure with two arrays.
14
15 Args:
16 nums1: First array (remains unchanged)
17 nums2: Second array (can be modified via add method)
18 """
19 # Store frequency count of elements in nums2 for O(1) lookup
20 self.frequency_map = Counter(nums2)
21 # Store reference to nums1 (immutable)
22 self.nums1 = nums1
23 # Store reference to nums2 (mutable)
24 self.nums2 = nums2
25
26 def add(self, index: int, val: int) -> None:
27 """
28 Add a value to an element at a specific index in nums2.
29
30 Args:
31 index: The index of the element in nums2 to update
32 val: The value to add to nums2[index]
33 """
34 # Decrease the count of the old value in frequency map
35 self.frequency_map[self.nums2[index]] -= 1
36
37 # Update the actual value in nums2
38 self.nums2[index] += val
39
40 # Increase the count of the new value in frequency map
41 self.frequency_map[self.nums2[index]] += 1
42
43 def count(self, tot: int) -> int:
44 """
45 Count the number of pairs (i, j) where nums1[i] + nums2[j] == tot.
46
47 Args:
48 tot: The target sum to find
49
50 Returns:
51 The count of valid pairs
52 """
53 # For each element in nums1, check how many elements in nums2
54 # can form the target sum with it
55 total_pairs = sum(self.frequency_map[tot - num1_element]
56 for num1_element in self.nums1)
57 return total_pairs
58
59
60# Your FindSumPairs object will be instantiated and called as such:
61# obj = FindSumPairs(nums1, nums2)
62# obj.add(index, val)
63# param_2 = obj.count(tot)
64
1class FindSumPairs {
2 // Array to store the first set of numbers (immutable)
3 private int[] nums1;
4
5 // Array to store the second set of numbers (mutable)
6 private int[] nums2;
7
8 // HashMap to store frequency count of each number in nums2
9 // Key: number value, Value: frequency count
10 private Map<Integer, Integer> frequencyMap = new HashMap<>();
11
12 /**
13 * Constructor to initialize the data structure with two arrays
14 * @param nums1 First array of integers (will not be modified)
15 * @param nums2 Second array of integers (can be modified via add method)
16 */
17 public FindSumPairs(int[] nums1, int[] nums2) {
18 this.nums1 = nums1;
19 this.nums2 = nums2;
20
21 // Build frequency map for nums2 array
22 for (int num : nums2) {
23 // Increment count for each number in nums2
24 frequencyMap.merge(num, 1, Integer::sum);
25 }
26 }
27
28 /**
29 * Add a value to an element in nums2 at the specified index
30 * @param index The index of the element in nums2 to modify
31 * @param val The value to add to nums2[index]
32 */
33 public void add(int index, int val) {
34 // Decrease frequency count for the old value
35 int oldValue = nums2[index];
36 frequencyMap.merge(oldValue, -1, Integer::sum);
37
38 // Update the value in nums2
39 nums2[index] += val;
40
41 // Increase frequency count for the new value
42 int newValue = nums2[index];
43 frequencyMap.merge(newValue, 1, Integer::sum);
44 }
45
46 /**
47 * Count the number of pairs (i, j) where nums1[i] + nums2[j] equals tot
48 * @param tot The target sum to find
49 * @return The count of valid pairs
50 */
51 public int count(int tot) {
52 int pairCount = 0;
53
54 // For each element in nums1, check if complement exists in nums2
55 for (int num1 : nums1) {
56 int complement = tot - num1;
57 // Add the frequency of the complement in nums2 to the count
58 pairCount += frequencyMap.getOrDefault(complement, 0);
59 }
60
61 return pairCount;
62 }
63}
64
65/**
66 * Your FindSumPairs object will be instantiated and called as such:
67 * FindSumPairs obj = new FindSumPairs(nums1, nums2);
68 * obj.add(index, val);
69 * int param_2 = obj.count(tot);
70 */
71
1class FindSumPairs {
2private:
3 vector<int> nums1; // First array (immutable)
4 vector<int> nums2; // Second array (mutable)
5 unordered_map<int, int> frequency; // Frequency map for nums2 elements
6
7public:
8 /**
9 * Constructor: Initializes the data structure with two integer arrays
10 * @param nums1: First array of integers
11 * @param nums2: Second array of integers
12 */
13 FindSumPairs(vector<int>& nums1, vector<int>& nums2) {
14 this->nums1 = nums1;
15 this->nums2 = nums2;
16
17 // Build frequency map for nums2
18 for (int num : nums2) {
19 ++frequency[num];
20 }
21 }
22
23 /**
24 * Adds a value to an element at specified index in nums2
25 * @param index: Index of element in nums2 to modify
26 * @param val: Value to add to nums2[index]
27 */
28 void add(int index, int val) {
29 // Decrease frequency count for old value
30 --frequency[nums2[index]];
31
32 // Update the value at index
33 nums2[index] += val;
34
35 // Increase frequency count for new value
36 ++frequency[nums2[index]];
37 }
38
39 /**
40 * Counts the number of pairs (i, j) where nums1[i] + nums2[j] == tot
41 * @param tot: Target sum to find
42 * @return: Number of valid pairs
43 */
44 int count(int tot) {
45 int result = 0;
46
47 // For each element in nums1, find complementary values in nums2
48 for (int num : nums1) {
49 int complement = tot - num;
50
51 // Add frequency of complement in nums2 to result
52 result += frequency[complement];
53 }
54
55 return result;
56 }
57};
58
59/**
60 * Your FindSumPairs object will be instantiated and called as such:
61 * FindSumPairs* obj = new FindSumPairs(nums1, nums2);
62 * obj->add(index, val);
63 * int param_2 = obj->count(tot);
64 */
65
1// Arrays to store the two input arrays
2let nums1Array: number[];
3let nums2Array: number[];
4// Map to store frequency count of elements in nums2
5let frequencyMap: Map<number, number>;
6
7/**
8 * Initializes the data structures with two arrays
9 * @param nums1 - First array of numbers
10 * @param nums2 - Second array of numbers
11 */
12function FindSumPairs(nums1: number[], nums2: number[]): void {
13 nums1Array = nums1;
14 nums2Array = nums2;
15 frequencyMap = new Map<number, number>();
16
17 // Build frequency map for nums2
18 for (const num of nums2) {
19 frequencyMap.set(num, (frequencyMap.get(num) || 0) + 1);
20 }
21}
22
23/**
24 * Adds a value to an element at the specified index in nums2
25 * @param index - Index of the element in nums2 to modify
26 * @param val - Value to add to the element
27 */
28function add(index: number, val: number): void {
29 // Decrease frequency count of the old value
30 const oldValue = nums2Array[index];
31 frequencyMap.set(oldValue, frequencyMap.get(oldValue)! - 1);
32
33 // Update the value in nums2
34 nums2Array[index] += val;
35
36 // Increase frequency count of the new value
37 const newValue = nums2Array[index];
38 frequencyMap.set(newValue, (frequencyMap.get(newValue) || 0) + 1);
39}
40
41/**
42 * Counts the number of pairs (i, j) where nums1[i] + nums2[j] equals tot
43 * @param tot - Target sum to find pairs for
44 * @returns Number of valid pairs
45 */
46function count(tot: number): number {
47 // For each element in nums1, find how many elements in nums2 sum to tot
48 return nums1Array.reduce((totalCount, num1Element) => {
49 const complement = tot - num1Element;
50 return totalCount + (frequencyMap.get(complement) || 0);
51 }, 0);
52}
53
Time and Space Complexity
Time Complexity:
__init__
:O(m)
wherem
is the length ofnums2
, as we need to build a Counter fromnums2
add
:O(1)
for updating the counter and modifying the array elementcount
:O(n)
wheren
is the length ofnums1
, as we iterate through each element innums1
and performO(1)
counter lookups
Overall, if count
is called q
times, the total time complexity for all count
operations is O(n × q)
.
Space Complexity:
- The Counter
self.cnt
stores at mostm
unique values fromnums2
:O(m)
- We store references to
nums1
andnums2
:O(n + m)
- Overall space complexity:
O(n + m)
, which simplifies toO(m)
when considering the dominant term as mentioned in the reference (since we're primarily concerned with the additional space used by the data structure beyond the input arrays themselves)
The reference answer states O(n × q)
for time complexity and O(m)
for space complexity, where n
and m
are the lengths of nums1
and nums2
respectively, and q
is the number of times the count
method is called.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Forgetting to Update the Frequency Map Before Modifying the Array
A critical mistake is updating nums2[index]
before adjusting the frequency map. This leads to incorrect frequency tracking.
Incorrect Implementation:
def add(self, index: int, val: int) -> None:
# WRONG: Modifying array first loses the old value reference
self.nums2[index] += val
self.frequency_map[self.nums2[index]] += 1
# The old value's count is never decremented!
Why it fails: Once you modify nums2[index]
, you lose track of what the old value was, making it impossible to decrement its count in the frequency map.
Correct Solution:
def add(self, index: int, val: int) -> None:
# First: Decrement count of old value
self.frequency_map[self.nums2[index]] -= 1
# Second: Update the array
self.nums2[index] += val
# Third: Increment count of new value
self.frequency_map[self.nums2[index]] += 1
2. Not Handling Negative Frequency Counts
When elements are removed from the frequency map (count becomes 0 or negative), not cleaning up can lead to memory inefficiency or logical errors in some implementations.
Potential Issue:
def add(self, index: int, val: int) -> None:
self.frequency_map[self.nums2[index]] -= 1
# If frequency becomes 0, the key still exists with value 0
# This won't cause incorrect results but uses unnecessary memory
Optimized Solution:
def add(self, index: int, val: int) -> None:
old_val = self.nums2[index]
self.frequency_map[old_val] -= 1
if self.frequency_map[old_val] == 0:
del self.frequency_map[old_val] # Clean up zero entries
self.nums2[index] += val
self.frequency_map[self.nums2[index]] += 1
3. Using Regular Dictionary Instead of Counter
Using a regular dictionary without proper initialization can cause KeyError when accessing non-existent keys.
Problematic Code:
def __init__(self, nums1: List[int], nums2: List[int]):
self.frequency_map = {} # Regular dict
for num in nums2:
self.frequency_map[num] = self.frequency_map.get(num, 0) + 1
def count(self, tot: int) -> int:
# This will raise KeyError if (tot - x) doesn't exist in frequency_map
return sum(self.frequency_map[tot - x] for x in self.nums1)
Solution:
Use Counter
(which returns 0 for missing keys) or handle missing keys explicitly:
def count(self, tot: int) -> int:
return sum(self.frequency_map.get(tot - x, 0) for x in self.nums1)
4. Modifying nums1 Instead of nums2
The problem specifically states that only nums2
can be modified. Some might mistakenly try to optimize by choosing which array to modify based on size.
Wrong Assumption:
def __init__(self, nums1: List[int], nums2: List[int]):
# WRONG: Trying to optimize by tracking the smaller array
if len(nums1) < len(nums2):
self.frequency_map = Counter(nums1) # Wrong array!
self.mutable = nums1 # Can't modify nums1!
Correct Approach:
Always track nums2
in the frequency map since that's the only array that can be modified, regardless of relative sizes.
Which of the following uses divide and conquer strategy?
Recommended Readings
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!