Maximum Good Subarray Sum
MedPrompt
You are given an array nums of length n and a positive integer k.
A subarray of nums is called good if the absolute difference between its first and last element is exactly k, in other words, the subarray nums[i..j] is good if |nums[i] - nums[j]| == k.
Return the maximum sum of a good subarray of nums. If there are no good subarrays, return 0.
Example 1:
Input: nums = [1,2,3,4,5,6], k = 1
Output: 11
Explanation: The absolute difference between the first and last element must be 1 for a good subarray. All the good subarrays are: [1,2], [2,3], [3,4], [4,5], and [5,6]. The maximum subarray sum is 11 for the subarray [5,6].Example 2:
Input: nums = [-1,3,2,4,5], k = 3
Output: 11
Explanation: The absolute difference between the first and last element must be 3 for a good subarray. All the good subarrays are: [-1,3,2], and [2,4,5]. The maximum subarray sum is 11 for the subarray [2,4,5].Example 3:
Input: nums = [-1,-2,-3,-4], k = 2
Output: -6
Explanation: The absolute difference between the first and last element must be 2 for a good subarray. All the good subarrays are: [-1,-2,-3], and [-2,-3,-4]. The maximum subarray sum is -6 for the subarray [-1,-2,-3].
Constraints:
2 <= nums.length <= 105-109 <= nums[i] <= 1091 <= k <= 109
Approaches
2 approaches with complexity analysis and trade-offs.
This approach iterates through all possible subarrays, checks if they are 'good', and calculates their sum to find the maximum. To optimize sum calculation, it uses a precomputed prefix sum array.
Algorithm
- Create a prefix sum array
prefixof sizen+1, whereprefix[i]stores the sum of elements fromnums[0]tonums[i-1]. - Initialize a variable
maxSumto a very small value (e.g.,Long.MIN_VALUE) and a boolean flagfoundGoodSubarraytofalse. - Iterate through the array with a start index
ifrom0ton-1. - For each
i, iterate with an end indexjfromi+1ton-1. - Check if the subarray
nums[i..j]is good:if (Math.abs(nums[i] - nums[j]) == k). - If it's a good subarray, calculate its sum using the prefix sum array:
currentSum = prefix[j+1] - prefix[i]. - Update
maxSum = Math.max(maxSum, currentSum)and setfoundGoodSubarraytotrue. - After the loops, if
foundGoodSubarrayistrue, returnmaxSum. Otherwise, return0.
Walkthrough
The most straightforward way to solve this problem is to examine every possible subarray. A subarray is defined by its start and end indices, i and j.
We can use nested loops to generate all pairs of (i, j) where i < j. For each pair, we check if it forms a 'good' subarray by testing the condition |nums[i] - nums[j]| == k. If the condition is met, we then need to calculate the sum of the elements in nums[i..j].
A naive sum calculation for each good subarray would involve another loop, leading to an O(n^3) solution. We can optimize this by pre-calculating prefix sums. A prefix sum array, prefix, allows us to find the sum of any subarray nums[i..j] in O(1) time using the formula sum = prefix[j+1] - prefix[i]. This optimization reduces the overall time complexity to O(n^2).
We maintain a variable maxSum to keep track of the maximum sum found so far. Since the problem requires returning 0 if no good subarray exists, we use a boolean flag to track whether at least one has been found.
class Solution { public long maximumSubarraySum(int[] nums, int k) { int n = nums.length; long[] prefix = new long[n + 1]; for (int i = 0; i < n; i++) { prefix[i + 1] = prefix[i] + nums[i]; } long maxSum = Long.MIN_VALUE; boolean found = false; for (int i = 0; i < n; i++) { for (int j = i + 1; j < n; j++) { if (Math.abs((long)nums[i] - nums[j]) == k) { found = true; long currentSum = prefix[j + 1] - prefix[i]; if (currentSum > maxSum) { maxSum = currentSum; } } } } return found ? maxSum : 0; }}Complexity
Time
O(n^2). The nested loops iterate through all possible start and end points of subarrays, resulting in a quadratic number of pairs to check. The prefix sum calculation takes O(n), but it's dominated by the nested loops.
Space
O(n). We need an auxiliary array of size `n+1` to store the prefix sums.
Trade-offs
Pros
Simple to understand and implement.
Cons
Inefficient for large inputs due to its quadratic time complexity, which will likely result in a 'Time Limit Exceeded' error on platforms with large test cases.
This approach improves upon the brute-force method by using a hash map to efficiently find the required starting elements for good subarrays. It processes the array in a single pass, achieving linear time complexity.
Algorithm
- Compute a prefix sum array
prefixof sizen+1. - Initialize
maxSum = Long.MIN_VALUE, a booleanfoundGoodSubarray = false, and a hash mapminPrefixSumMapto store(value, min_prefix_sum)pairs. - Iterate through the array with index
jfrom0ton-1. - For the current element
nums[j], consider it as the end of a subarray. Look for a valid starti < j. - Check for the two possible starting values:
target1 = nums[j] - kandtarget2 = nums[j] + k. - If
minPrefixSumMapcontainstarget1, calculate the sumcurrentSum = prefix[j+1] - minPrefixSumMap.get(target1)and updatemaxSumandfoundGoodSubarray. - If
minPrefixSumMapcontainstarget2, do the same. - After checking, update the map for
nums[j]to be a potential start for future subarrays. Storeprefix[j]for the keynums[j], keeping the minimum if the key already exists:map.put(nums[j], Math.min(map.getOrDefault(nums[j], Long.MAX_VALUE), prefix[j])). - After the loop, if a good subarray was found, return
maxSum. Otherwise, return0.
Walkthrough
To optimize the search for the maximum good subarray sum, we can rephrase the problem. For each possible endpoint j of a subarray, we want to find the best possible starting point i < j that satisfies |nums[i] - nums[j]| == k and maximizes the sum sum(nums[i..j]).
Using prefix sums, the sum is prefix[j+1] - prefix[i]. For a fixed j, maximizing this sum is equivalent to minimizing prefix[i].
The condition |nums[i] - nums[j]| == k means we are looking for a starting element nums[i] that is equal to either nums[j] - k or nums[j] + k.
This leads to an efficient O(n) algorithm. We iterate through the array from left to right (index j from 0 to n-1). We use a hash map to store the minimum prefix sum encountered so far for each number value. Let this map be minPrefixSumMap, where map.get(v) gives the minimum prefix[i] for all i processed so far where nums[i] == v.
In each iteration j:
- Find a good subarray: We check if the map contains keys for
nums[j] - kornums[j] + k. If it does, we have found a potential starting pointi < j. We calculate the sum using the stored minimum prefix sum (prefix[j+1] - minPrefixSumMap.get(target)) and update ourmaxSum. - Update the map: We then update the map with the information from the current index
j. We recordprefix[j]as a possible minimum prefix sum for the valuenums[j]. This makesnums[j]available as a starting point for subsequent elements.
The order of these two steps is crucial. By checking for a subarray before updating the map, we ensure that we only consider starting indices i that are strictly less than the current ending index j.
import java.util.HashMap;import java.util.Map; class Solution { public long maximumSubarraySum(int[] nums, int k) { int n = nums.length; long[] prefix = new long[n + 1]; for (int i = 0; i < n; i++) { prefix[i + 1] = prefix[i] + nums[i]; } long maxSum = Long.MIN_VALUE; boolean found = false; Map<Integer, Long> minPrefixSumMap = new HashMap<>(); for (int j = 0; j < n; j++) { int val = nums[j]; // 1. Check for good subarrays ending at j int target1 = val - k; if (minPrefixSumMap.containsKey(target1)) { long currentSum = prefix[j + 1] - minPrefixSumMap.get(target1); maxSum = Math.max(maxSum, currentSum); found = true; } int target2 = val + k; if (minPrefixSumMap.containsKey(target2)) { long currentSum = prefix[j + 1] - minPrefixSumMap.get(target2); maxSum = Math.max(maxSum, currentSum); found = true; } // 2. Update map with info from index j long currentPrefix = prefix[j]; if (!minPrefixSumMap.containsKey(val) || currentPrefix < minPrefixSumMap.get(val)) { minPrefixSumMap.put(val, currentPrefix); } } return found ? maxSum : 0; }}Complexity
Time
O(n). We iterate through the array once. Hash map operations (get, put) take O(1) on average. The prefix sum calculation also takes O(n). The overall complexity is linear.
Space
O(n). We use an O(n) prefix sum array and a hash map that can store up to `n` unique elements in the worst case, leading to O(n) space.
Trade-offs
Pros
Highly efficient with linear time complexity, making it suitable for large inputs.
This is the optimal solution for the given constraints.
Cons
Requires extra space for the hash map and prefix sum array.
Slightly more complex to reason about than the brute-force approach.
Solutions
Solution
public class Solution { public long MaximumSubarraySum(int[] nums, int k) { Dictionary < int, long > p = new Dictionary < int, long > (); p[nums[0]] = 0 L; long s = 0; int n = nums.Length; long ans = long.MinValue; for (int i = 0; i < n; ++i) { s += nums[i]; if (p.ContainsKey(nums[i] - k)) { ans = Math.Max(ans, s - p[nums[i] - k]); } if (p.ContainsKey(nums[i] + k)) { ans = Math.Max(ans, s - p[nums[i] + k]); } if (i + 1 < n && (!p.ContainsKey(nums[i + 1]) || p[nums[i + 1]] > s)) { p[nums[i + 1]] = s; } } return ans == long.MinValue ? 0 : ans; }}class Solution {public long maximumSubarraySum(int[] nums, int k) { Map<Integer, Long> p = new HashMap<>(); p.put(nums[0], 0L); long s = 0; int n = nums.length; long ans = Long.MIN_VALUE; for (int i = 0; i < n; ++i) { s += nums[i]; if (p.containsKey(nums[i] - k)) { ans = Math.max(ans, s - p.get(nums[i] - k)); } if (p.containsKey(nums[i] + k)) { ans = Math.max(ans, s - p.get(nums[i] + k)); } if (i + 1 < n && (!p.containsKey(nums[i + 1]) || p.get(nums[i + 1]) > s)) { p.put(nums[i + 1], s); } } return ans == Long.MIN_VALUE ? 0 : ans; }}class Solution {public: long long maximumSubarraySum(vector<int> &nums, int k) { unordered_map<int, long long> p; p[nums[0]] = 0; long long s = 0; const int n = nums.size(); long long ans = LONG_LONG_MIN; for (int i = 0;; ++i) { s += nums[i]; auto it = p.find(nums[i] - k); if (it != p.end()) { ans = max(ans, s - it->second); } it = p.find(nums[i] + k); if (it != p.end()) { ans = max(ans, s - it->second); } if (i + 1 == n) { break; } it = p.find(nums[i + 1]); if (it == p.end() || it->second > s) { p[nums[i + 1]] = s; } } return ans == LONG_LONG_MIN ? 0 : ans; }};class Solution: def maximumSubarraySum(self, nums: List[int], k: int) -> int: ans = - inf p = {nums[0]: 0} s, n = 0, len(nums) for i, x in enumerate(nums): s += x if x - k in p: ans = max(ans, s - p[x - k]) if x + k in p: ans = max(ans, s - p[x + k]) if i + 1 < n and (nums[i + 1] not in p or p[nums[i + 1]] > s): p[nums[i + 1]] = s return 0 if ans == - inf else ansVideo 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.