Finding Pairs With a Certain Sum
MedPrompt
You are given two integer arrays nums1 and nums2. You are tasked to implement a data structure that supports queries of two types:
- Add a positive integer to an element of a given index in the array
nums2. - Count the number of pairs
(i, j)such thatnums1[i] + nums2[j]equals a given value (0 <= i < nums1.lengthand0 <= j < nums2.length).
Implement the FindSumPairs class:
FindSumPairs(int[] nums1, int[] nums2)Initializes theFindSumPairsobject with two integer arraysnums1andnums2.void add(int index, int val)Addsvaltonums2[index], i.e., applynums2[index] += val.int count(int tot)Returns the number of pairs(i, j)such thatnums1[i] + nums2[j] == tot.
Example 1:
,5,4
Constraints:
1 <= nums1.length <= 10001 <= nums2.length <= 1051 <= nums1[i] <= 1091 <= nums2[i] <= 1050 <= index < nums2.length1 <= val <= 1051 <= tot <= 109- At most
1000calls are made toaddandcounteach.
Approaches
2 approaches with complexity analysis and trade-offs.
This is the most straightforward approach where we directly translate the problem statement into code. We keep the original arrays and, for each count query, we iterate through all possible pairs from nums1 and nums2 to see how many of them sum up to the target value tot.
Algorithm
- Constructor
FindSumPairs(nums1, nums2): Store references to the input arraysnums1andnums2. add(index, val): Directly update the value in thenums2array at the given index:nums2[index] += val.count(tot):- Initialize a counter
pairCountto 0. - Use nested loops to iterate through every possible pair of elements
(nums1[i], nums2[j]). - For each pair, check if their sum equals
tot. - If
nums1[i] + nums2[j] == tot, incrementpairCount. - After checking all pairs, return
pairCount.
- Initialize a counter
Walkthrough
In this approach, the FindSumPairs class holds the two arrays. The add operation is very simple and efficient, as it only involves updating an element in nums2 at a specific index. The main drawback is the count method. It employs a nested loop structure: the outer loop goes through each element of nums1, and for each of these, the inner loop goes through every element of nums2. This exhaustive check of all pairs makes the count operation computationally expensive, especially with the given constraints where nums2 can be very large.
class FindSumPairs { private int[] nums1; private int[] nums2; public FindSumPairs(int[] nums1, int[] nums2) { this.nums1 = nums1; this.nums2 = nums2; } public void add(int index, int val) { nums2[index] += val; } public int count(int tot) { int pairCount = 0; for (int num1 : nums1) { for (int num2 : nums2) { if (num1 + num2 == tot) { pairCount++; } } } return pairCount; }}Complexity
Time
- **Constructor**: O(1) (or O(L1 + L2) if copying arrays). - **`add`**: O(1). - **`count`**: O(L1 * L2), where L1 and L2 are the lengths of `nums1` and `nums2` respectively. This is the bottleneck.
Space
O(1), as we only store references to the input arrays. If we make copies, it would be O(L1 + L2).
Trade-offs
Pros
Very simple to understand and implement.
The
addoperation is extremely fast, taking constant time.Requires minimal extra space.
Cons
The
countoperation is extremely slow, with a time complexity of O(L1 * L2). Given the problem constraints (L1 up to 1000, L2 up to 10^5), this will result in a 'Time Limit Exceeded' error on most platforms.
This optimized approach addresses the inefficiency of the brute-force count method. By using a hash map to store the frequencies of numbers in the larger array, nums2, we can significantly speed up the process of finding pairs. The key insight is that for any num1 from nums1, we need to find how many elements in nums2 are equal to tot - num1. A frequency map allows us to answer this query in constant time on average.
Algorithm
- Constructor
FindSumPairs(nums1, nums2):- Store
nums1andnums2. - Create a hash map,
freqMap, to store the frequency of each number innums2. - Iterate through
nums2and populatefreqMap.
- Store
add(index, val):- Get the old value
oldVal = nums2[index]. - Decrement the frequency of
oldValinfreqMap. - Update the array:
nums2[index] += val. - Get the new value
newVal = nums2[index]. - Increment the frequency of
newValinfreqMap.
- Get the old value
count(tot):- Initialize
pairCount = 0. - Iterate through each number
num1innums1. - Calculate the required complement:
complement = tot - num1. - Look up the frequency of
complementinfreqMapand add it topairCount. - Return
pairCount.
- Initialize
Walkthrough
We pre-process nums2 in the constructor by building a hash map that maps each unique number to its count. This allows the count(tot) operation to be much faster. Instead of a nested loop, we only need to loop through the smaller array, nums1. For each num1, we calculate the complement needed to reach tot and then perform a quick O(1) average time lookup in our hash map to find how many such complements exist in nums2.
The add(index, val) operation needs to be updated to keep the frequency map consistent. When a value in nums2 is changed, we must decrement the count of the old value and increment the count of the new value in the map. Given that nums1 is much smaller than nums2 and count is called frequently, this trade-off is highly beneficial.
import java.util.HashMap;import java.util.Map; class FindSumPairs { private int[] nums1; private int[] nums2; private Map<Integer, Integer> freq2; public FindSumPairs(int[] nums1, int[] nums2) { this.nums1 = nums1; this.nums2 = nums2; this.freq2 = new HashMap<>(); for (int num : nums2) { freq2.put(num, freq2.getOrDefault(num, 0) + 1); } } public void add(int index, int val) { int oldVal = nums2[index]; // Decrease the frequency of the old value freq2.put(oldVal, freq2.get(oldVal) - 1); if (freq2.get(oldVal) == 0) { freq2.remove(oldVal); } int newVal = oldVal + val; nums2[index] = newVal; // Increase the frequency of the new value freq2.put(newVal, freq2.getOrDefault(newVal, 0) + 1); } public int count(int tot) { int totalPairs = 0; for (int num1 : nums1) { int complement = tot - num1; totalPairs += freq2.getOrDefault(complement, 0); } return totalPairs; }}Complexity
Time
- **Constructor**: O(L2) to build the frequency map. - **`add`**: O(1) on average due to hash map operations. - **`count`**: O(L1) because we iterate through `nums1` and perform an O(1) lookup for each element.
Space
O(L2), for storing the frequency map of `nums2`. In the worst case, if all elements in `nums2` are unique, the map will have L2 entries.
Trade-offs
Pros
The
countoperation is significantly faster, with a time complexity of O(L1), making it highly efficient for the given constraints.The
addoperation remains fast, taking O(1) average time.This approach is well-balanced for the problem's specific constraints and call patterns.
Cons
Requires additional space to store the frequency map for
nums2, which can be up to O(L2) in the worst case.The
addoperation is slightly more complex as it needs to maintain the state of the frequency map.
Solutions
Solution
public class FindSumPairs { private int [] nums1 ; private int [] nums2 ; private Dictionary < int , int > cnt = new Dictionary < int , int >(); public FindSumPairs ( int [] nums1 , int [] nums2 ) { this . nums1 = nums1 ; this . nums2 = nums2 ; foreach ( int x in nums2 ) { if ( cnt . ContainsKey ( x )) { cnt [ x ]++; } else { cnt [ x ] = 1 ; } } } public void Add ( int index , int val ) { int oldVal = nums2 [ index ]; if ( cnt . TryGetValue ( oldVal , out int oldCount )) { if ( oldCount == 1 ) { cnt . Remove ( oldVal ); } else { cnt [ oldVal ] = oldCount - 1 ; } } nums2 [ index ] += val ; int newVal = nums2 [ index ]; if ( cnt . TryGetValue ( newVal , out int newCount )) { cnt [ newVal ] = newCount + 1 ; } else { cnt [ newVal ] = 1 ; } } public int Count ( int tot ) { int ans = 0 ; foreach ( int x in nums1 ) { int target = tot - x ; if ( cnt . TryGetValue ( target , out int count )) { ans += count ; } } return ans ; } } /** * Your FindSumPairs object will be instantiated and called as such: * FindSumPairs obj = new FindSumPairs(nums1, nums2); * obj.Add(index,val); * int param_2 = obj.Count(tot); */class FindSumPairs { private int [] nums1 ; private int [] nums2 ; private Map < Integer , Integer > cnt = new HashMap <>(); public FindSumPairs ( int [] nums1 , int [] nums2 ) { this . nums1 = nums1 ; this . nums2 = nums2 ; for ( int v : nums2 ) { cnt . put ( v , cnt . getOrDefault ( v , 0 ) + 1 ); } } public void add ( int index , int val ) { int old = nums2 [ index ]; cnt . put ( old , cnt . get ( old ) - 1 ); cnt . put ( old + val , cnt . getOrDefault ( old + val , 0 ) + 1 ); nums2 [ index ] += val ; } public int count ( int tot ) { int ans = 0 ; for ( int v : nums1 ) { ans += cnt . getOrDefault ( tot - v , 0 ); } return ans ; } } /** * Your FindSumPairs object will be instantiated and called as such: * FindSumPairs obj = new FindSumPairs(nums1, nums2); * obj.add(index,val); * int param_2 = obj.count(tot); *//** * @param {number[]} nums1 * @param {number[]} nums2 */ var FindSumPairs = function (nums1, nums2) { this.nums1 = nums1; this.nums2 = nums2; this.cnt = new Map(); for (const x of nums2) { this.cnt.set(x, (this.cnt.get(x) || 0) + 1); } };/** * @param {number} index * @param {number} val * @return {void} */ FindSumPairs.prototype.add = function (index, val) { const old = this.nums2[index]; this.cnt.set(old, this.cnt.get(old) - 1); this.nums2[index] += val; const now = this.nums2[index]; this.cnt.set(now, (this.cnt.get(now) || 0) + 1); };/** * @param {number} tot * @return {number} */ FindSumPairs.prototype.count = function (tot) { return this.nums1.reduce((acc, x) => acc + (this.cnt.get(tot - x) || 0), 0); }; /** * Your FindSumPairs object will be instantiated and called as such: * var obj = new FindSumPairs(nums1, nums2) * obj.add(index,val) * var param_2 = obj.count(tot) */class FindSumPairs : def __init__ ( self , nums1 : List [ int ], nums2 : List [ int ]): self . nums1 = nums1 self . nums2 = nums2 self . cnt = Counter ( nums2 ) def add ( self , index : int , val : int ) -> None : old = self . nums2 [ index ] self . cnt [ old ] -= 1 self . cnt [ old + val ] += 1 self . nums2 [ index ] += val def count ( self , tot : int ) -> int : return sum ( self . cnt [ tot - v ] for v in self . nums1 ) # Your FindSumPairs object will be instantiated and called as such: # obj = FindSumPairs(nums1, nums2) # obj.add(index,val) # param_2 = obj.count(tot)class FindSumPairs { public: FindSumPairs ( vector < int >& nums1 , vector < int >& nums2 ) { this -> nums1 = nums1 ; this -> nums2 = nums2 ; for ( int & v : nums2 ) { ++ cnt [ v ]; } } void add ( int index , int val ) { int old = nums2 [ index ]; -- cnt [ old ]; ++ cnt [ old + val ]; nums2 [ index ] += val ; } int count ( int tot ) { int ans = 0 ; for ( int & v : nums1 ) { ans += cnt [ tot - v ]; } return ans ; } private: vector < int > nums1 ; vector < int > nums2 ; unordered_map < int , int > cnt ; }; /** * Your FindSumPairs object will be instantiated and called as such: * FindSumPairs* obj = new FindSumPairs(nums1, nums2); * obj->add(index,val); * int param_2 = obj->count(tot); */Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.