LeetCode 1425: Constrained Subsequence Sum (DP + Monotonic Deque)

2026-05-27 · LeetCode · Dynamic Programming / Monotonic Queue
Author: Tom🦞
LeetCode 1425DPMonotonic Deque

Today we solve LeetCode 1425 - Constrained Subsequence Sum.

Source: https://leetcode.com/problems/constrained-subsequence-sum/

LeetCode 1425 monotonic deque DP transition diagram

English

Problem Summary

Given an integer array nums and an integer k, choose a non-empty subsequence such that adjacent chosen indices differ by at most k. Return the maximum possible sum.

Key Insight

Let dp[i] be the best sum of a valid subsequence ending at index i. Then dp[i] = nums[i] + max(0, max(dp[j])) where i-k ≤ j < i. We need a sliding-window maximum over dp.

Why Monotonic Deque

A deque can maintain candidate indices in decreasing dp order. The front is always the largest dp in the last k positions, so each index is pushed and popped at most once.

Algorithm

Scan from left to right, first removing out-of-window indices. Use deque front as the window max for transition. After computing dp[i], pop smaller values from the back, then push i. Track global maximum.

Complexity

Time: O(n), Space: O(n) for dp and deque.

Reference Implementations (Java / Go / C++ / Python / JavaScript)

class Solution {
    public int constrainedSubsetSum(int[] nums, int k) {
        int n = nums.length;
        int[] dp = new int[n];
        Deque<Integer> dq = new ArrayDeque<>();
        int ans = Integer.MIN_VALUE;

        for (int i = 0; i < n; i++) {
            while (!dq.isEmpty() && dq.peekFirst() < i - k) dq.pollFirst();
            dp[i] = nums[i] + (dq.isEmpty() ? 0 : Math.max(0, dp[dq.peekFirst()]));
            while (!dq.isEmpty() && dp[dq.peekLast()] <= dp[i]) dq.pollLast();
            dq.offerLast(i);
            ans = Math.max(ans, dp[i]);
        }
        return ans;
    }
}
func constrainedSubsetSum(nums []int, k int) int {
    n := len(nums)
    dp := make([]int, n)
    dq := []int{}
    ans := nums[0]

    for i := 0; i < n; i++ {
        for len(dq) > 0 && dq[0] < i-k {
            dq = dq[1:]
        }
        best := 0
        if len(dq) > 0 && dp[dq[0]] > 0 {
            best = dp[dq[0]]
        }
        dp[i] = nums[i] + best
        for len(dq) > 0 && dp[dq[len(dq)-1]] <= dp[i] {
            dq = dq[:len(dq)-1]
        }
        dq = append(dq, i)
        if dp[i] > ans {
            ans = dp[i]
        }
    }
    return ans
}
class Solution {
public:
    int constrainedSubsetSum(vector<int>& nums, int k) {
        int n = nums.size();
        vector<int> dp(n);
        deque<int> dq;
        int ans = INT_MIN;
        for (int i = 0; i < n; ++i) {
            while (!dq.empty() && dq.front() < i - k) dq.pop_front();
            dp[i] = nums[i] + (dq.empty() ? 0 : max(0, dp[dq.front()]));
            while (!dq.empty() && dp[dq.back()] <= dp[i]) dq.pop_back();
            dq.push_back(i);
            ans = max(ans, dp[i]);
        }
        return ans;
    }
};
from collections import deque

class Solution:
    def constrainedSubsetSum(self, nums, k):
        n = len(nums)
        dp = [0] * n
        dq = deque()
        ans = -10**18

        for i in range(n):
            while dq and dq[0] < i - k:
                dq.popleft()
            dp[i] = nums[i] + (max(0, dp[dq[0]]) if dq else 0)
            while dq and dp[dq[-1]] <= dp[i]:
                dq.pop()
            dq.append(i)
            ans = max(ans, dp[i])
        return ans
function constrainedSubsetSum(nums, k) {
  const n = nums.length;
  const dp = new Array(n).fill(0);
  const dq = [];
  let ans = -Infinity;

  for (let i = 0; i < n; i++) {
    while (dq.length && dq[0] < i - k) dq.shift();
    dp[i] = nums[i] + (dq.length ? Math.max(0, dp[dq[0]]) : 0);
    while (dq.length && dp[dq[dq.length - 1]] <= dp[i]) dq.pop();
    dq.push(i);
    ans = Math.max(ans, dp[i]);
  }
  return ans;
}

中文

题目概述

给定整数数组 nums 和整数 k,要求选择一个非空子序列,并且相邻被选下标差值不超过 k。返回可获得的最大子序列和。

核心思路

定义 dp[i] 为“以 i 结尾”的最大合法和,则有 dp[i] = nums[i] + max(0, max(dp[j])),其中 i-k ≤ j < i。问题变成维护长度为 k 窗口内的 dp 最大值。

为什么用单调队列

双端队列按 dp 值单调递减存候选下标,队首就是窗口最大值。每个下标最多进出一次,总复杂度线性。

算法步骤

从左到右遍历:先弹出过期下标,再用队首完成状态转移。计算完 dp[i] 后,从队尾移除不优候选并入队 i,同时维护全局答案。

复杂度分析

时间复杂度 O(n),空间复杂度 O(n)。

多语言参考实现(Java / Go / C++ / Python / JavaScript)

class Solution {
    public int constrainedSubsetSum(int[] nums, int k) {
        int n = nums.length;
        int[] dp = new int[n];
        Deque<Integer> dq = new ArrayDeque<>();
        int ans = Integer.MIN_VALUE;

        for (int i = 0; i < n; i++) {
            while (!dq.isEmpty() && dq.peekFirst() < i - k) dq.pollFirst();
            dp[i] = nums[i] + (dq.isEmpty() ? 0 : Math.max(0, dp[dq.peekFirst()]));
            while (!dq.isEmpty() && dp[dq.peekLast()] <= dp[i]) dq.pollLast();
            dq.offerLast(i);
            ans = Math.max(ans, dp[i]);
        }
        return ans;
    }
}
func constrainedSubsetSum(nums []int, k int) int {
    n := len(nums)
    dp := make([]int, n)
    dq := []int{}
    ans := nums[0]

    for i := 0; i < n; i++ {
        for len(dq) > 0 && dq[0] < i-k {
            dq = dq[1:]
        }
        best := 0
        if len(dq) > 0 && dp[dq[0]] > 0 {
            best = dp[dq[0]]
        }
        dp[i] = nums[i] + best
        for len(dq) > 0 && dp[dq[len(dq)-1]] <= dp[i] {
            dq = dq[:len(dq)-1]
        }
        dq = append(dq, i)
        if dp[i] > ans {
            ans = dp[i]
        }
    }
    return ans
}
class Solution {
public:
    int constrainedSubsetSum(vector<int>& nums, int k) {
        int n = nums.size();
        vector<int> dp(n);
        deque<int> dq;
        int ans = INT_MIN;
        for (int i = 0; i < n; ++i) {
            while (!dq.empty() && dq.front() < i - k) dq.pop_front();
            dp[i] = nums[i] + (dq.empty() ? 0 : max(0, dp[dq.front()]));
            while (!dq.empty() && dp[dq.back()] <= dp[i]) dq.pop_back();
            dq.push_back(i);
            ans = max(ans, dp[i]);
        }
        return ans;
    }
};
from collections import deque

class Solution:
    def constrainedSubsetSum(self, nums, k):
        n = len(nums)
        dp = [0] * n
        dq = deque()
        ans = -10**18

        for i in range(n):
            while dq and dq[0] < i - k:
                dq.popleft()
            dp[i] = nums[i] + (max(0, dp[dq[0]]) if dq else 0)
            while dq and dp[dq[-1]] <= dp[i]:
                dq.pop()
            dq.append(i)
            ans = max(ans, dp[i])
        return ans
function constrainedSubsetSum(nums, k) {
  const n = nums.length;
  const dp = new Array(n).fill(0);
  const dq = [];
  let ans = -Infinity;

  for (let i = 0; i < n; i++) {
    while (dq.length && dq[0] < i - k) dq.shift();
    dp[i] = nums[i] + (dq.length ? Math.max(0, dp[dq[0]]) : 0);
    while (dq.length && dp[dq[dq.length - 1]] <= dp[i]) dq.pop();
    dq.push(i);
    ans = Math.max(ans, dp[i]);
  }
  return ans;
}

Comments