Given an integer array nums
and an integer k
, you are asked to find the maximum sum of a non-empty subsequence of nums
such that for every pair of consecutive elements in the subsequence, their indices in nums
differ by at most k
. In other words, for a chosen subsequence with indices i_1, i_2, ..., i_m
, it must be true that 1 ≤ i_{j+1} - i_j ≤ k
for all valid j
. Each element from nums
can be used at most once, and you must select at least one element.
nums
and an integer k
.k
of each other.
At first glance, this problem looks similar to the classic "maximum subarray sum" problem, but with an added twist: when picking elements to form the subsequence, we can only choose the next number within k
indices of the current one.
A brute-force approach would be to try every possible subsequence that fits the constraints, but this quickly becomes infeasible for large arrays due to the exponential number of possibilities. Instead, we need to find a way to efficiently compute, for each position, the best sum we can achieve ending at that position, considering the constraint on indices.
This hints at using dynamic programming, where for each index, we store the maximum sum of a valid subsequence ending at that index. The challenge is to efficiently find the best previous sum within the last k
indices, which leads us to consider data structures that support fast maximum queries in a sliding window.
We solve the problem using dynamic programming and a monotonic deque to maintain the maximum in a sliding window.
dp[i]
be the maximum sum of a valid constrained subsequence ending at index i
.dp[i] = nums[i]
because we can always start a new subsequence with just nums[i]
.i
, to extend a subsequence, we look at dp[j]
for all j
in [i-k, i-1]
and pick the maximum. Then dp[i] = nums[i] + max(0, max(dp[j]))
.max(0, ...)
is because we can always start fresh at i
if all previous sums are negative.i
, looking back k
steps is O(k), resulting in O(nk) time.dp
values in the last k
steps. This allows us to get the maximum in O(1) time per step, and maintain the deque in amortized O(1) per step.dp
.This approach is efficient and leverages the properties of sliding windows and monotonic queues for optimal performance.
Sample Input: nums = [10,2,-10,5,20], k = 2
dp[0] = 10
dp[0]
(within k=2): max = 10
dp[1] = nums[1] + max(0, 10) = 2 + 10 = 12
dp[0], dp[1]
(within k=2): max = 12
dp[2] = -10 + max(0, 12) = 2
dp[1], dp[2]
(within k=2): max = 12
dp[3] = 5 + max(0,12) = 17
dp[2], dp[3]
(within k=2): max = 17
dp[4] = 20 + max(0,17) = 37
The answer is 37, achieved by the subsequence [10,2,5,20]
(indices 0,1,3,4).
dp
array and the deque (which never holds more than k
elements).The use of the deque ensures that finding the sliding window maximum is efficient, avoiding redundant comparisons.
The "Constrained Subsequence Sum" problem requires finding the maximum sum of a subsequence where consecutive elements are at most k
apart. A naive approach is too slow, but by recognizing the structure of the problem, we use dynamic programming and a monotonic queue to efficiently track the maximum sum for each possible endpoint. This approach elegantly combines classic DP with advanced data structures, resulting in an O(n) solution that is both fast and easy to understand once the core ideas are clear.
from collections import deque
class Solution:
def constrainedSubsetSum(self, nums, k):
n = len(nums)
dp = nums[:]
dq = deque()
res = nums[0]
for i in range(n):
# Remove indices out of window
if dq and dq[0] < i - k:
dq.popleft()
# If dq not empty, add max in window
if dq:
dp[i] = max(dp[i], nums[i] + dp[dq[0]])
# Maintain decreasing order in dq
while dq and dp[dq[-1]] < dp[i]:
dq.pop()
dq.append(i)
res = max(res, dp[i])
return res
#include <vector>
#include <deque>
#include <algorithm>
using namespace std;
class Solution {
public:
int constrainedSubsetSum(vector<int>& nums, int k) {
int n = nums.size();
vector<int> dp(nums);
deque<int> dq;
int res = nums[0];
for (int i = 0; i < n; ++i) {
if (!dq.empty() && dq.front() < i - k)
dq.pop_front();
if (!dq.empty())
dp[i] = max(dp[i], nums[i] + dp[dq.front()]);
while (!dq.empty() && dp[dq.back()] < dp[i])
dq.pop_back();
dq.push_back(i);
res = max(res, dp[i]);
}
return res;
}
};
import java.util.*;
class Solution {
public int constrainedSubsetSum(int[] nums, int k) {
int n = nums.length;
int[] dp = Arrays.copyOf(nums, n);
Deque<Integer> dq = new ArrayDeque<>();
int res = nums[0];
for (int i = 0; i < n; i++) {
if (!dq.isEmpty() && dq.peekFirst() < i - k)
dq.pollFirst();
if (!dq.isEmpty())
dp[i] = Math.max(dp[i], nums[i] + dp[dq.peekFirst()]);
while (!dq.isEmpty() && dp[dq.peekLast()] < dp[i])
dq.pollLast();
dq.offerLast(i);
res = Math.max(res, dp[i]);
}
return res;
}
}
/**
* @param {number[]} nums
* @param {number} k
* @return {number}
*/
var constrainedSubsetSum = function(nums, k) {
const n = nums.length;
const dp = nums.slice();
const dq = [];
let res = nums[0];
for (let i = 0; i < n; i++) {
// Remove indices out of the window
if (dq.length && dq[0] < i - k) dq.shift();
if (dq.length) dp[i] = Math.max(dp[i], nums[i] + dp[dq[0]]);
while (dq.length && dp[dq[dq.length - 1]] < dp[i]) dq.pop();
dq.push(i);
res = Math.max(res, dp[i]);
}
return res;
};