Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,69 +1,89 @@
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 {
private 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.
* @param nums The array from which subarrays will be considered.
* 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).
*
* @return The maximum sum of any distinct-element subarray of size K. If no such subarray exists, returns 0.
* @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.
* 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++;
}
}
if (!flag) {
currentSet.remove(nums[i - 1]);
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);
}
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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,157 @@
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);
}
}