mirror of
https://github.com/TheAlgorithms/Java.git
synced 2026-03-13 08:40:43 +08:00
refactor: Enhance docs, code, add tests in `MaximumSumOfDistinctSubar… (#6649)
* refactor: Enhance docs, code, add tests in `MaximumSumOfDistinctSubarraysWithLengthK` * Fix * Fix spotbug * Fix * Fix * Fix * Fix
This commit is contained in:
@@ -1,14 +1,26 @@
|
||||
package com.thealgorithms.others;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* References: https://en.wikipedia.org/wiki/Streaming_algorithm
|
||||
* Algorithm to find the maximum sum of a subarray of size K with all distinct
|
||||
* elements.
|
||||
*
|
||||
* This model involves computing the maximum sum of subarrays of a fixed size \( K \) from a stream of integers.
|
||||
* As the stream progresses, elements from the end of the window are removed, and new elements from the stream are added.
|
||||
* This implementation uses a sliding window approach with a hash map to
|
||||
* efficiently
|
||||
* track element frequencies within the current window. The algorithm maintains
|
||||
* a window
|
||||
* of size K and slides it across the array, ensuring all elements in the window
|
||||
* are distinct.
|
||||
*
|
||||
* Time Complexity: O(n) where n is the length of the input array
|
||||
* Space Complexity: O(k) for storing elements in the hash map
|
||||
*
|
||||
* @see <a href="https://en.wikipedia.org/wiki/Streaming_algorithm">Streaming
|
||||
* Algorithm</a>
|
||||
* @see <a href="https://en.wikipedia.org/wiki/Sliding_window_protocol">Sliding
|
||||
* Window</a>
|
||||
* @author Swarga-codes (https://github.com/Swarga-codes)
|
||||
*/
|
||||
public final class MaximumSumOfDistinctSubarraysWithLengthK {
|
||||
@@ -16,54 +28,62 @@ public final class MaximumSumOfDistinctSubarraysWithLengthK {
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the maximum sum of a subarray of size K consisting of distinct elements.
|
||||
* Finds the maximum sum of a subarray of size K consisting of distinct
|
||||
* elements.
|
||||
*
|
||||
* @param k The size of the subarray.
|
||||
* The algorithm uses a sliding window technique with a frequency map to track
|
||||
* the count of each element in the current window. A window is valid only if
|
||||
* all K elements are distinct (frequency map size equals K).
|
||||
*
|
||||
* @param k The size of the subarray. Must be non-negative.
|
||||
* @param nums The array from which subarrays will be considered.
|
||||
*
|
||||
* @return The maximum sum of any distinct-element subarray of size K. If no such subarray exists, returns 0.
|
||||
* @return The maximum sum of any distinct-element subarray of size K.
|
||||
* Returns 0 if no such subarray exists or if k is 0 or negative.
|
||||
* @throws IllegalArgumentException if k is negative
|
||||
*/
|
||||
public static long maximumSubarraySum(int k, int... nums) {
|
||||
if (nums.length < k) {
|
||||
if (k <= 0 || nums == null || nums.length < k) {
|
||||
return 0;
|
||||
}
|
||||
long masSum = 0; // Variable to store the maximum sum of distinct subarrays
|
||||
long currentSum = 0; // Variable to store the sum of the current subarray
|
||||
Set<Integer> currentSet = new HashSet<>(); // Set to track distinct elements in the current subarray
|
||||
|
||||
// Initialize the first window
|
||||
long maxSum = 0;
|
||||
long currentSum = 0;
|
||||
Map<Integer, Integer> frequencyMap = new HashMap<>();
|
||||
|
||||
// Initialize the first window of size k
|
||||
for (int i = 0; i < k; i++) {
|
||||
currentSum += nums[i];
|
||||
currentSet.add(nums[i]);
|
||||
frequencyMap.put(nums[i], frequencyMap.getOrDefault(nums[i], 0) + 1);
|
||||
}
|
||||
// If the first window contains distinct elements, update maxSum
|
||||
if (currentSet.size() == k) {
|
||||
masSum = currentSum;
|
||||
|
||||
// Check if the first window has all distinct elements
|
||||
if (frequencyMap.size() == k) {
|
||||
maxSum = currentSum;
|
||||
}
|
||||
|
||||
// Slide the window across the array
|
||||
for (int i = 1; i < nums.length - k + 1; i++) {
|
||||
// Update the sum by removing the element that is sliding out and adding the new element
|
||||
currentSum = currentSum - nums[i - 1];
|
||||
currentSum = currentSum + nums[i + k - 1];
|
||||
int j = i;
|
||||
boolean flag = false; // flag value which says that the subarray contains distinct elements
|
||||
while (j < i + k && currentSet.size() < k) {
|
||||
if (nums[i - 1] == nums[j]) {
|
||||
flag = true;
|
||||
break;
|
||||
} else {
|
||||
j++;
|
||||
}
|
||||
for (int i = k; i < nums.length; i++) {
|
||||
// Remove the leftmost element from the window
|
||||
int leftElement = nums[i - k];
|
||||
currentSum -= leftElement;
|
||||
int leftFrequency = frequencyMap.get(leftElement);
|
||||
if (leftFrequency == 1) {
|
||||
frequencyMap.remove(leftElement);
|
||||
} else {
|
||||
frequencyMap.put(leftElement, leftFrequency - 1);
|
||||
}
|
||||
if (!flag) {
|
||||
currentSet.remove(nums[i - 1]);
|
||||
}
|
||||
currentSet.add(nums[i + k - 1]);
|
||||
// If the current window has distinct elements, compare and possibly update maxSum
|
||||
if (currentSet.size() == k && masSum < currentSum) {
|
||||
masSum = currentSum;
|
||||
|
||||
// Add the new rightmost element to the window
|
||||
int rightElement = nums[i];
|
||||
currentSum += rightElement;
|
||||
frequencyMap.put(rightElement, frequencyMap.getOrDefault(rightElement, 0) + 1);
|
||||
|
||||
// If all elements in the window are distinct, update maxSum if needed
|
||||
if (frequencyMap.size() == k && currentSum > maxSum) {
|
||||
maxSum = currentSum;
|
||||
}
|
||||
}
|
||||
return masSum; // the final maximum sum
|
||||
|
||||
return maxSum;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,20 +3,157 @@ package com.thealgorithms.others;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
public class MaximumSumOfDistinctSubarraysWithLengthKTest {
|
||||
/**
|
||||
* Test class for {@link MaximumSumOfDistinctSubarraysWithLengthK}.
|
||||
*
|
||||
* This class contains comprehensive test cases to verify the correctness of the
|
||||
* maximum subarray sum algorithm with distinct elements constraint.
|
||||
*/
|
||||
class MaximumSumOfDistinctSubarraysWithLengthKTest {
|
||||
|
||||
/**
|
||||
* Parameterized test for various input scenarios.
|
||||
*
|
||||
* @param expected the expected maximum sum
|
||||
* @param k the subarray size
|
||||
* @param arr the input array
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@MethodSource("inputStream")
|
||||
void testMaximumSubarraySum(int expected, int k, int[] arr) {
|
||||
void testMaximumSubarraySum(long expected, int k, int[] arr) {
|
||||
assertEquals(expected, MaximumSumOfDistinctSubarraysWithLengthK.maximumSubarraySum(k, arr));
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides test cases for the parameterized test.
|
||||
*
|
||||
* Test cases cover:
|
||||
* - Normal cases with distinct and duplicate elements
|
||||
* - Edge cases (empty array, k = 0, k > array length)
|
||||
* - Single element arrays
|
||||
* - Arrays with all duplicates
|
||||
* - Negative numbers
|
||||
* - Large sums
|
||||
*
|
||||
* @return stream of test arguments
|
||||
*/
|
||||
private static Stream<Arguments> inputStream() {
|
||||
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}),
|
||||
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}));
|
||||
return Stream.of(
|
||||
// Normal case: [5, 4, 2] has distinct elements with sum 11, but [4, 2, 9] also
|
||||
// distinct with sum 15
|
||||
Arguments.of(15L, 3, new int[] {1, 5, 4, 2, 9, 9, 9}),
|
||||
// All elements are same, no distinct subarray of size 3
|
||||
Arguments.of(0L, 3, new int[] {4, 4, 4}),
|
||||
// First three have duplicates, but [1, 2, 3] are distinct with sum 6, wait
|
||||
// [9,1,2] has sum 12
|
||||
Arguments.of(12L, 3, new int[] {9, 9, 9, 1, 2, 3}),
|
||||
// k = 0, should return 0
|
||||
Arguments.of(0L, 0, new int[] {9, 9, 9}),
|
||||
// k > array length, should return 0
|
||||
Arguments.of(0L, 5, new int[] {9, 9, 9}),
|
||||
// k = 1, single element (always distinct)
|
||||
Arguments.of(9L, 1, new int[] {9, 2, 3, 7}),
|
||||
// All distinct elements, size matches array
|
||||
Arguments.of(15L, 5, new int[] {1, 2, 3, 4, 5}),
|
||||
// Array with negative numbers
|
||||
Arguments.of(6L, 3, new int[] {-1, 2, 3, 1, -2, 4}),
|
||||
// Single element array
|
||||
Arguments.of(10L, 1, new int[] {10}),
|
||||
// All duplicates with k = 2
|
||||
Arguments.of(0L, 2, new int[] {7, 7, 7, 7}),
|
||||
// Empty array
|
||||
Arguments.of(0L, 3, new int[] {}),
|
||||
// k much larger than array length
|
||||
Arguments.of(0L, 10, new int[] {1, 2, 3}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test with a larger array and larger k value.
|
||||
*/
|
||||
@Test
|
||||
void testLargerArray() {
|
||||
int[] arr = new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
|
||||
long result = MaximumSumOfDistinctSubarraysWithLengthK.maximumSubarraySum(5, arr);
|
||||
// Maximum sum with 5 distinct elements: [6,7,8,9,10] = 40
|
||||
assertEquals(40L, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test with negative k value.
|
||||
*/
|
||||
@Test
|
||||
void testNegativeK() {
|
||||
int[] arr = new int[] {1, 2, 3, 4, 5};
|
||||
long result = MaximumSumOfDistinctSubarraysWithLengthK.maximumSubarraySum(-1, arr);
|
||||
assertEquals(0L, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test with null array.
|
||||
*/
|
||||
@Test
|
||||
void testNullArray() {
|
||||
int[] nullArray = null;
|
||||
long result = MaximumSumOfDistinctSubarraysWithLengthK.maximumSubarraySum(3, new int[][] {nullArray}[0]);
|
||||
assertEquals(0L, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test with array containing duplicates at boundaries.
|
||||
*/
|
||||
@Test
|
||||
void testDuplicatesAtBoundaries() {
|
||||
int[] arr = new int[] {1, 1, 2, 3, 4, 4};
|
||||
// [2, 3, 4] is the only valid window with sum 9
|
||||
long result = MaximumSumOfDistinctSubarraysWithLengthK.maximumSubarraySum(3, arr);
|
||||
assertEquals(9L, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test with large numbers to verify long return type.
|
||||
*/
|
||||
@Test
|
||||
void testLargeNumbers() {
|
||||
int[] arr = new int[] {1000000, 2000000, 3000000, 4000000};
|
||||
// All elements are distinct, max sum with k=3 is [2000000, 3000000, 4000000] =
|
||||
// 9000000
|
||||
long result = MaximumSumOfDistinctSubarraysWithLengthK.maximumSubarraySum(3, arr);
|
||||
assertEquals(9000000L, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test where multiple windows have the same maximum sum.
|
||||
*/
|
||||
@Test
|
||||
void testMultipleMaxWindows() {
|
||||
int[] arr = new int[] {1, 2, 3, 4, 3, 2, 1};
|
||||
// Windows [1,2,3], [2,3,4], [4,3,2], [3,2,1] - max is [2,3,4] = 9
|
||||
long result = MaximumSumOfDistinctSubarraysWithLengthK.maximumSubarraySum(3, arr);
|
||||
assertEquals(9L, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test with only two elements and k=2.
|
||||
*/
|
||||
@Test
|
||||
void testTwoElementsDistinct() {
|
||||
int[] arr = new int[] {5, 10};
|
||||
long result = MaximumSumOfDistinctSubarraysWithLengthK.maximumSubarraySum(2, arr);
|
||||
assertEquals(15L, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test with only two elements (duplicates) and k=2.
|
||||
*/
|
||||
@Test
|
||||
void testTwoElementsDuplicate() {
|
||||
int[] arr = new int[] {5, 5};
|
||||
long result = MaximumSumOfDistinctSubarraysWithLengthK.maximumSubarraySum(2, arr);
|
||||
assertEquals(0L, result);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user