Skip to content

Commit 4858ec9

Browse files
authored
refactor: Enhance docs, code, add tests in `MaximumSumOfDistinctSubar… (#6649)
* refactor: Enhance docs, code, add tests in `MaximumSumOfDistinctSubarraysWithLengthK` * Fix * Fix spotbug * Fix * Fix * Fix * Fix
1 parent e1773e9 commit 4858ec9

File tree

2 files changed

+200
-43
lines changed

2 files changed

+200
-43
lines changed
Lines changed: 59 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,89 @@
11
package com.thealgorithms.others;
22

3-
import java.util.HashSet;
4-
import java.util.Set;
3+
import java.util.HashMap;
4+
import java.util.Map;
55

66
/**
7-
* References: https://en.wikipedia.org/wiki/Streaming_algorithm
7+
* Algorithm to find the maximum sum of a subarray of size K with all distinct
8+
* elements.
89
*
9-
* This model involves computing the maximum sum of subarrays of a fixed size \( K \) from a stream of integers.
10-
* As the stream progresses, elements from the end of the window are removed, and new elements from the stream are added.
10+
* This implementation uses a sliding window approach with a hash map to
11+
* efficiently
12+
* track element frequencies within the current window. The algorithm maintains
13+
* a window
14+
* of size K and slides it across the array, ensuring all elements in the window
15+
* are distinct.
1116
*
17+
* Time Complexity: O(n) where n is the length of the input array
18+
* Space Complexity: O(k) for storing elements in the hash map
19+
*
20+
* @see <a href="https://en.wikipedia.org/wiki/Streaming_algorithm">Streaming
21+
* Algorithm</a>
22+
* @see <a href="https://en.wikipedia.org/wiki/Sliding_window_protocol">Sliding
23+
* Window</a>
1224
* @author Swarga-codes (https://github.com/Swarga-codes)
1325
*/
1426
public final class MaximumSumOfDistinctSubarraysWithLengthK {
1527
private MaximumSumOfDistinctSubarraysWithLengthK() {
1628
}
1729

1830
/**
19-
* Finds the maximum sum of a subarray of size K consisting of distinct elements.
31+
* Finds the maximum sum of a subarray of size K consisting of distinct
32+
* elements.
2033
*
21-
* @param k The size of the subarray.
22-
* @param nums The array from which subarrays will be considered.
34+
* The algorithm uses a sliding window technique with a frequency map to track
35+
* the count of each element in the current window. A window is valid only if
36+
* all K elements are distinct (frequency map size equals K).
2337
*
24-
* @return The maximum sum of any distinct-element subarray of size K. If no such subarray exists, returns 0.
38+
* @param k The size of the subarray. Must be non-negative.
39+
* @param nums The array from which subarrays will be considered.
40+
* @return The maximum sum of any distinct-element subarray of size K.
41+
* Returns 0 if no such subarray exists or if k is 0 or negative.
42+
* @throws IllegalArgumentException if k is negative
2543
*/
2644
public static long maximumSubarraySum(int k, int... nums) {
27-
if (nums.length < k) {
45+
if (k <= 0 || nums == null || nums.length < k) {
2846
return 0;
2947
}
30-
long masSum = 0; // Variable to store the maximum sum of distinct subarrays
31-
long currentSum = 0; // Variable to store the sum of the current subarray
32-
Set<Integer> currentSet = new HashSet<>(); // Set to track distinct elements in the current subarray
3348

34-
// Initialize the first window
49+
long maxSum = 0;
50+
long currentSum = 0;
51+
Map<Integer, Integer> frequencyMap = new HashMap<>();
52+
53+
// Initialize the first window of size k
3554
for (int i = 0; i < k; i++) {
3655
currentSum += nums[i];
37-
currentSet.add(nums[i]);
56+
frequencyMap.put(nums[i], frequencyMap.getOrDefault(nums[i], 0) + 1);
3857
}
39-
// If the first window contains distinct elements, update maxSum
40-
if (currentSet.size() == k) {
41-
masSum = currentSum;
58+
59+
// Check if the first window has all distinct elements
60+
if (frequencyMap.size() == k) {
61+
maxSum = currentSum;
4262
}
63+
4364
// Slide the window across the array
44-
for (int i = 1; i < nums.length - k + 1; i++) {
45-
// Update the sum by removing the element that is sliding out and adding the new element
46-
currentSum = currentSum - nums[i - 1];
47-
currentSum = currentSum + nums[i + k - 1];
48-
int j = i;
49-
boolean flag = false; // flag value which says that the subarray contains distinct elements
50-
while (j < i + k && currentSet.size() < k) {
51-
if (nums[i - 1] == nums[j]) {
52-
flag = true;
53-
break;
54-
} else {
55-
j++;
56-
}
57-
}
58-
if (!flag) {
59-
currentSet.remove(nums[i - 1]);
65+
for (int i = k; i < nums.length; i++) {
66+
// Remove the leftmost element from the window
67+
int leftElement = nums[i - k];
68+
currentSum -= leftElement;
69+
int leftFrequency = frequencyMap.get(leftElement);
70+
if (leftFrequency == 1) {
71+
frequencyMap.remove(leftElement);
72+
} else {
73+
frequencyMap.put(leftElement, leftFrequency - 1);
6074
}
61-
currentSet.add(nums[i + k - 1]);
62-
// If the current window has distinct elements, compare and possibly update maxSum
63-
if (currentSet.size() == k && masSum < currentSum) {
64-
masSum = currentSum;
75+
76+
// Add the new rightmost element to the window
77+
int rightElement = nums[i];
78+
currentSum += rightElement;
79+
frequencyMap.put(rightElement, frequencyMap.getOrDefault(rightElement, 0) + 1);
80+
81+
// If all elements in the window are distinct, update maxSum if needed
82+
if (frequencyMap.size() == k && currentSum > maxSum) {
83+
maxSum = currentSum;
6584
}
6685
}
67-
return masSum; // the final maximum sum
86+
87+
return maxSum;
6888
}
6989
}

src/test/java/com/thealgorithms/others/MaximumSumOfDistinctSubarraysWithLengthKTest.java

Lines changed: 141 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,157 @@
33
import static org.junit.jupiter.api.Assertions.assertEquals;
44

55
import java.util.stream.Stream;
6+
import org.junit.jupiter.api.Test;
67
import org.junit.jupiter.params.ParameterizedTest;
78
import org.junit.jupiter.params.provider.Arguments;
89
import org.junit.jupiter.params.provider.MethodSource;
910

10-
public class MaximumSumOfDistinctSubarraysWithLengthKTest {
11+
/**
12+
* Test class for {@link MaximumSumOfDistinctSubarraysWithLengthK}.
13+
*
14+
* This class contains comprehensive test cases to verify the correctness of the
15+
* maximum subarray sum algorithm with distinct elements constraint.
16+
*/
17+
class MaximumSumOfDistinctSubarraysWithLengthKTest {
1118

19+
/**
20+
* Parameterized test for various input scenarios.
21+
*
22+
* @param expected the expected maximum sum
23+
* @param k the subarray size
24+
* @param arr the input array
25+
*/
1226
@ParameterizedTest
1327
@MethodSource("inputStream")
14-
void testMaximumSubarraySum(int expected, int k, int[] arr) {
28+
void testMaximumSubarraySum(long expected, int k, int[] arr) {
1529
assertEquals(expected, MaximumSumOfDistinctSubarraysWithLengthK.maximumSubarraySum(k, arr));
1630
}
1731

32+
/**
33+
* Provides test cases for the parameterized test.
34+
*
35+
* Test cases cover:
36+
* - Normal cases with distinct and duplicate elements
37+
* - Edge cases (empty array, k = 0, k > array length)
38+
* - Single element arrays
39+
* - Arrays with all duplicates
40+
* - Negative numbers
41+
* - Large sums
42+
*
43+
* @return stream of test arguments
44+
*/
1845
private static Stream<Arguments> inputStream() {
19-
return Stream.of(Arguments.of(15, 3, new int[] {1, 5, 4, 2, 9, 9, 9}), Arguments.of(0, 3, new int[] {4, 4, 4}), Arguments.of(12, 3, new int[] {9, 9, 9, 1, 2, 3}), Arguments.of(0, 0, new int[] {9, 9, 9}), Arguments.of(0, 5, new int[] {9, 9, 9}), Arguments.of(9, 1, new int[] {9, 2, 3, 7}),
20-
Arguments.of(15, 5, new int[] {1, 2, 3, 4, 5}), Arguments.of(6, 3, new int[] {-1, 2, 3, 1, -2, 4}), Arguments.of(10, 1, new int[] {10}), Arguments.of(0, 2, new int[] {7, 7, 7, 7}), Arguments.of(0, 3, new int[] {}), Arguments.of(0, 10, new int[] {1, 2, 3}));
46+
return Stream.of(
47+
// Normal case: [5, 4, 2] has distinct elements with sum 11, but [4, 2, 9] also
48+
// distinct with sum 15
49+
Arguments.of(15L, 3, new int[] {1, 5, 4, 2, 9, 9, 9}),
50+
// All elements are same, no distinct subarray of size 3
51+
Arguments.of(0L, 3, new int[] {4, 4, 4}),
52+
// First three have duplicates, but [1, 2, 3] are distinct with sum 6, wait
53+
// [9,1,2] has sum 12
54+
Arguments.of(12L, 3, new int[] {9, 9, 9, 1, 2, 3}),
55+
// k = 0, should return 0
56+
Arguments.of(0L, 0, new int[] {9, 9, 9}),
57+
// k > array length, should return 0
58+
Arguments.of(0L, 5, new int[] {9, 9, 9}),
59+
// k = 1, single element (always distinct)
60+
Arguments.of(9L, 1, new int[] {9, 2, 3, 7}),
61+
// All distinct elements, size matches array
62+
Arguments.of(15L, 5, new int[] {1, 2, 3, 4, 5}),
63+
// Array with negative numbers
64+
Arguments.of(6L, 3, new int[] {-1, 2, 3, 1, -2, 4}),
65+
// Single element array
66+
Arguments.of(10L, 1, new int[] {10}),
67+
// All duplicates with k = 2
68+
Arguments.of(0L, 2, new int[] {7, 7, 7, 7}),
69+
// Empty array
70+
Arguments.of(0L, 3, new int[] {}),
71+
// k much larger than array length
72+
Arguments.of(0L, 10, new int[] {1, 2, 3}));
73+
}
74+
75+
/**
76+
* Test with a larger array and larger k value.
77+
*/
78+
@Test
79+
void testLargerArray() {
80+
int[] arr = new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
81+
long result = MaximumSumOfDistinctSubarraysWithLengthK.maximumSubarraySum(5, arr);
82+
// Maximum sum with 5 distinct elements: [6,7,8,9,10] = 40
83+
assertEquals(40L, result);
84+
}
85+
86+
/**
87+
* Test with negative k value.
88+
*/
89+
@Test
90+
void testNegativeK() {
91+
int[] arr = new int[] {1, 2, 3, 4, 5};
92+
long result = MaximumSumOfDistinctSubarraysWithLengthK.maximumSubarraySum(-1, arr);
93+
assertEquals(0L, result);
94+
}
95+
96+
/**
97+
* Test with null array.
98+
*/
99+
@Test
100+
void testNullArray() {
101+
int[] nullArray = null;
102+
long result = MaximumSumOfDistinctSubarraysWithLengthK.maximumSubarraySum(3, new int[][] {nullArray}[0]);
103+
assertEquals(0L, result);
104+
}
105+
106+
/**
107+
* Test with array containing duplicates at boundaries.
108+
*/
109+
@Test
110+
void testDuplicatesAtBoundaries() {
111+
int[] arr = new int[] {1, 1, 2, 3, 4, 4};
112+
// [2, 3, 4] is the only valid window with sum 9
113+
long result = MaximumSumOfDistinctSubarraysWithLengthK.maximumSubarraySum(3, arr);
114+
assertEquals(9L, result);
115+
}
116+
117+
/**
118+
* Test with large numbers to verify long return type.
119+
*/
120+
@Test
121+
void testLargeNumbers() {
122+
int[] arr = new int[] {1000000, 2000000, 3000000, 4000000};
123+
// All elements are distinct, max sum with k=3 is [2000000, 3000000, 4000000] =
124+
// 9000000
125+
long result = MaximumSumOfDistinctSubarraysWithLengthK.maximumSubarraySum(3, arr);
126+
assertEquals(9000000L, result);
127+
}
128+
129+
/**
130+
* Test where multiple windows have the same maximum sum.
131+
*/
132+
@Test
133+
void testMultipleMaxWindows() {
134+
int[] arr = new int[] {1, 2, 3, 4, 3, 2, 1};
135+
// Windows [1,2,3], [2,3,4], [4,3,2], [3,2,1] - max is [2,3,4] = 9
136+
long result = MaximumSumOfDistinctSubarraysWithLengthK.maximumSubarraySum(3, arr);
137+
assertEquals(9L, result);
138+
}
139+
140+
/**
141+
* Test with only two elements and k=2.
142+
*/
143+
@Test
144+
void testTwoElementsDistinct() {
145+
int[] arr = new int[] {5, 10};
146+
long result = MaximumSumOfDistinctSubarraysWithLengthK.maximumSubarraySum(2, arr);
147+
assertEquals(15L, result);
148+
}
149+
150+
/**
151+
* Test with only two elements (duplicates) and k=2.
152+
*/
153+
@Test
154+
void testTwoElementsDuplicate() {
155+
int[] arr = new int[] {5, 5};
156+
long result = MaximumSumOfDistinctSubarraysWithLengthK.maximumSubarraySum(2, arr);
157+
assertEquals(0L, result);
21158
}
22159
}

0 commit comments

Comments
 (0)