Skip to content

Commit 30ee311

Browse files
Pairs with Certain Sum - Leetcode 1865
1 parent 171568b commit 30ee311

File tree

3 files changed

+219
-0
lines changed

3 files changed

+219
-0
lines changed
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package array.pairs_with_certian_sum;
2+
3+
import java.util.HashMap;
4+
import java.util.Map;
5+
6+
/**
7+
* Implements a data structure to find pairs with a certain sum from two arrays.
8+
*
9+
* <p>This class is optimized for frequent {@code count} queries. It achieves this by
10+
* pre-processing {@code nums2} into a frequency map. This allows the {@code count}
11+
* operation to run in O(N) time (where N is the length of {@code nums1}), and the
12+
* {@code add} operation to run in O(1) time.
13+
*/
14+
public class FrequencyMap {
15+
private final int[] nums1;
16+
private final int[] nums2;
17+
private final Map<Integer, Integer> freqMap;
18+
19+
/**
20+
* Initializes the data structure with two integer arrays.
21+
*
22+
* <p>The constructor stores references to the arrays and builds a frequency map
23+
* from {@code nums2} to enable fast lookups.
24+
*
25+
* <p><b>Complexity:</b> O(M), where M is the length of {@code nums2}, due to
26+
* building the frequency map.
27+
*
28+
* @param nums1 The first integer array, which remains static.
29+
* @param nums2 The second integer array, which can be modified.
30+
*/
31+
public FrequencyMap(int[] nums1, int[] nums2) {
32+
this.nums1 = nums1;
33+
this.nums2 = nums2;
34+
this.freqMap = new HashMap<>();
35+
for (int num : nums2) {
36+
freqMap.put(num, freqMap.getOrDefault(num, 0) + 1);
37+
}
38+
}
39+
40+
/**
41+
* Adds a value to an element in {@code nums2} and updates the frequency map.
42+
*
43+
* <p>This operation first decrements the frequency of the old value at the given
44+
* index. If the frequency becomes zero, the entry is removed from the map. It then
45+
* updates the array element and increments the frequency of the new value.
46+
*
47+
* <p><b>Complexity:</b> O(1) on average for HashMap operations.
48+
*
49+
* @param index The index in {@code nums2} to modify.
50+
* @param val The positive integer value to add.
51+
*/
52+
public void add(int index, int val) {
53+
// First, decrement the count of the old number.
54+
int oldVal = nums2[index];
55+
freqMap.computeIfPresent(oldVal, (key, count) -> (count == 1) ? null : count - 1);
56+
57+
// Update the number in the array.
58+
nums2[index] += val;
59+
60+
// Then, increment the count of the new number.
61+
int newVal = nums2[index];
62+
freqMap.put(newVal, freqMap.getOrDefault(newVal, 0) + 1);
63+
}
64+
65+
/**
66+
* Counts the number of pairs (i, j) such that nums1[i] + nums2[j] == total.
67+
*
68+
* <p>This method iterates through the static {@code nums1} array. For each element
69+
* {@code n1}, it calculates the required complement ({@code total - n1}) and looks
70+
* up its frequency in the map of {@code nums2}.
71+
*
72+
* <p><b>Complexity:</b> O(N), where N is the length of {@code nums1}.
73+
*
74+
* @param total The target sum to find.
75+
* @return The total number of pairs that sum to {@code total}.
76+
*/
77+
public int count(int total) {
78+
int result = 0;
79+
for (int num : nums1) {
80+
// For each number in nums1, find how many complements exist in nums2.
81+
result += freqMap.getOrDefault(total - num, 0);
82+
}
83+
return result;
84+
}
85+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
* Provides an implementation for the "Find Pairs With a Certain Sum" problem.
3+
*
4+
* <p>The problem requires designing a data structure that manages two integer arrays,
5+
* {@code nums1} and {@code nums2}, and supports two types of operations:
6+
* <ol>
7+
* <li><b>Add:</b> Update an element in {@code nums2} by adding a positive value to it.</li>
8+
* <li><b>Count:</b> Count the number of pairs {@code (i, j)} such that
9+
* {@code nums1[i] + nums2[j]} equals a given total.</li>
10+
* </ol>
11+
*
12+
* <h3>Core Implementation Strategy</h3>
13+
*
14+
* <p>A naive approach to the {@code count} operation would be to iterate through both
15+
* arrays for every query, resulting in an O(N * M) time complexity, which is too slow.
16+
*
17+
* <p>A much more efficient strategy is to pre-process one of the arrays into a frequency
18+
* map (e.g., a {@code HashMap}). Since {@code nums2} is the array that gets modified, it is
19+
* the ideal candidate for this map. By storing the counts of each number in {@code nums2},
20+
* the {@code count} operation can be optimized significantly.
21+
*
22+
* <p>The logic is as follows:
23+
* <ul>
24+
* <li><b>Initialization:</b> Store {@code nums1} as is. Create a frequency map from
25+
* {@code nums2} to count occurrences of each number.</li>
26+
* <li><b>{@code count(total)}:</b> Iterate through the static {@code nums1} array. For each
27+
* number {@code n1} in {@code nums1}, calculate the required complement {@code target = total - n1}.
28+
* Look up how many times {@code target} appears in the {@code nums2} frequency map and
29+
* add that count to the total. This reduces the complexity of a count query to O(length of nums1).</li>
30+
* <li><b>{@code add(index, val)}:</b> When a value in {@code nums2} is updated, the frequency
31+
* map must also be updated. Decrement the count of the old value ({@code nums2[index]})
32+
* and increment the count of the new value ({@code nums2[index] + val}). This keeps the
33+
* map in sync with the array.</li>
34+
* </ul>
35+
*
36+
* @version 1.0
37+
* @see <a href="https://leetcode.com/problems/finding-pairs-with-a-certain-sum">LeetCode 1865: Find Pairs With a Certain Sum</a>
38+
* @since 1.0
39+
*/
40+
package array.pairs_with_certian_sum;
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package array.pairs_with_certian_sum;
2+
3+
import org.junit.jupiter.api.DisplayName;
4+
import org.junit.jupiter.api.Test;
5+
6+
import static org.junit.jupiter.api.Assertions.assertEquals;
7+
8+
/**
9+
* Test suite for the {@link FrequencyMap} class.
10+
*/
11+
class FrequencyMapTest {
12+
13+
@Test
14+
@DisplayName("Should correctly handle the provided LeetCode example scenario")
15+
void shouldPassLeetCodeScenario() {
16+
// Input:
17+
// ["FindSumPairs", "count", "add", "count", "count", "add", "add", "count"]
18+
// [[[1, 1, 2, 2, 2, 3], [1, 4, 5, 2, 5, 4]], [7], [3, 2], [8], [4], [0, 1], [1, 1], [7]]
19+
// Expected Output:
20+
// [null, 8, null, 2, 1, null, null, 11]
21+
22+
// Initialize the class
23+
int[] nums1 = {1, 1, 2, 2, 2, 3};
24+
int[] nums2 = {1, 4, 5, 2, 5, 4};
25+
FrequencyMap findSumPairs = new FrequencyMap(nums1, nums2);
26+
27+
// count(7) -> Expected: 8
28+
// Pairs: (2,5), (2,5), (2,5), (2,5), (2,5), (2,5) from nums1[2,3,4] and nums2[2,4] -> 3*2=6
29+
// Pairs: (3,4), (3,4) from nums1[5] and nums2[1,5] -> 1*2=2. Total = 8.
30+
assertEquals(8, findSumPairs.count(7), "Initial count for total 7 should be 8");
31+
32+
// add(3, 2) -> nums2 becomes [1, 4, 5, 4, 5, 4]
33+
findSumPairs.add(3, 2);
34+
35+
// count(8) -> Expected: 2
36+
// Pairs: (3,5), (3,5) from nums1[5] and nums2[2,4] -> 1*2=2. Total = 2.
37+
assertEquals(2, findSumPairs.count(8), "Count for total 8 after first add should be 2");
38+
39+
// count(4) -> Expected: 1
40+
// Pair: (3,1) from nums1[5] and nums2[0]. Total = 1.
41+
assertEquals(1, findSumPairs.count(4), "Count for total 4 should be 1");
42+
43+
// add(0, 1) -> nums2 becomes [2, 4, 5, 4, 5, 4]
44+
findSumPairs.add(0, 1);
45+
// add(1, 1) -> nums2 becomes [2, 5, 5, 4, 5, 4]
46+
findSumPairs.add(1, 1);
47+
48+
// count(7) -> Expected: 11
49+
// Pairs: (2,5), (2,5), (2,5) from nums1[2,3,4] and nums2[1,2,4] -> 3*3=9
50+
// Pairs: (3,4), (3,4) from nums1[5] and nums2[3,5] -> 1*2=2. Total = 11.
51+
assertEquals(11, findSumPairs.count(7), "Count for total 7 after final adds should be 11");
52+
}
53+
54+
@Test
55+
@DisplayName("Should return 0 when no pairs sum to the target")
56+
void shouldReturnZeroWhenNoPairsMatch() {
57+
int[] nums1 = {10, 20, 30};
58+
int[] nums2 = {1, 2, 3};
59+
FrequencyMap findSumPairs = new FrequencyMap(nums1, nums2);
60+
61+
assertEquals(0, findSumPairs.count(100), "Should return 0 for a total that cannot be formed");
62+
assertEquals(0, findSumPairs.count(5), "Should return 0 for a total that is too small");
63+
}
64+
65+
@Test
66+
@DisplayName("Should correctly update counts after an add operation")
67+
void shouldUpdateCountsAfterAdd() {
68+
int[] nums1 = {1, 2, 3};
69+
int[] nums2 = {1, 1, 1};
70+
FrequencyMap findSumPairs = new FrequencyMap(nums1, nums2);
71+
72+
// Initially, 3 pairs sum to 2: (1,1), (1,1), (1,1)
73+
assertEquals(3, findSumPairs.count(2), "Initial count for total 2 should be 3");
74+
75+
// Add 1 to nums2[0], making it [2, 1, 1]
76+
findSumPairs.add(0, 1);
77+
78+
// Now, 2 pairs sum to 2: (1,1), (1,1)
79+
assertEquals(2, findSumPairs.count(2), "Count for total 2 should decrease to 2 after add");
80+
// And 1 pair sums to 4: (2,2), (3, 1), (3, 1)
81+
assertEquals(3, findSumPairs.count(4), "Count for total 4 should become 1 after add");
82+
}
83+
84+
@Test
85+
@DisplayName("Should handle arrays with duplicate numbers correctly")
86+
void shouldHandleDuplicatesInBothArrays() {
87+
int[] nums1 = {2, 2, 2};
88+
int[] nums2 = {3, 3};
89+
FrequencyMap findSumPairs = new FrequencyMap(nums1, nums2);
90+
91+
// Each of the 3 '2's in nums1 can be paired with each of the 2 '3's in nums2
92+
assertEquals(6, findSumPairs.count(5), "Should be 3 * 2 = 6 pairs that sum to 5");
93+
}
94+
}

0 commit comments

Comments
 (0)