leetcode
leetcode 1101 ~ 1150
带限制的子序列和

带限制的子序列和

难度:

标签:

题目描述

代码结果

运行时间: 260 ms, 内存: 27.7 MB


// Java Stream API solution
// For this problem, the use of streams is not very efficient compared to the standard approach using deque,
// but we can still illustrate a functional approach. However, Java Stream API does not have direct support
// for efficiently solving this problem due to the need for maintaining a sliding window maximum.
// The solution provided uses imperative style because of the efficiency constraints.
// Here, we utilize the same deque method but just wrap it in stream-like operations.
import java.util.Deque;
import java.util.LinkedList;
import java.util.stream.IntStream;

public class Solution {
    public int constrainedSubsetSum(int[] nums, int k) {
        Deque<Integer> deque = new LinkedList<>();
        return IntStream.range(0, nums.length).map(i -> {
            if (!deque.isEmpty() && deque.peekFirst() < i - k) {
                deque.pollFirst();
            }
            int max = nums[i] + (deque.isEmpty() ? 0 : Math.max(0, nums[deque.peekFirst()]));
            while (!deque.isEmpty() && max > nums[deque.peekLast()]) {
                deque.pollLast();
            }
            deque.offerLast(i);
            return max;
        }).max().orElse(0);
    }
}

解释

方法:

这个题解使用了动态规划加单调队列的优化方法。动态规划数组`nums`的每个元素`nums[i]`被更新为以`nums[i]`结尾的最大子序列和。考虑到每个元素只能与前面距离不超过`k`的元素组成子序列,所以每次计算`nums[i]`时,只需要考虑`nums[i-k]`到`nums[i-1]`这个区间内的最大值。为了高效地获取这个区间内的最大值,使用了单调队列`stk`,这个队列存储的是索引,并且保证队列中的元素对应的`nums`值是单调递减的。对于每个新的`i`, 我们从队头移除超过距离限制的索引,然后将`nums[i]`与队头所指元素的`nums`值相加(如果这个值是正的),这样`nums[i]`就成了以`i`为结尾的最大子序列和。然后我们将当前的`i`添加到队列中,同时保持队列的单调性。这样,队列的头总是当前窗口的最大值。最终,遍历完成后的最大`nums`值即为答案。

时间复杂度:

O(n)

空间复杂度:

O(k)

代码细节讲解

🦆
为什么在动态规划中选择使用单调队列而不是其他数据结构如堆(优先队列)来维护区间最大值?
单调队列相比于堆(优先队列)在这个应用场景中有更高的效率。单调队列能够以O(1)的时间复杂度实现获取最大值,同时添加和删除操作平均也能达到O(1)的时间复杂度。而堆的插入和删除操作时间复杂度为O(log k),在这里k为子序列的长度限制。特别是在需要频繁移除特定位置元素(如超出滑动窗口范围的元素)时,单调队列能够直接从队列头部快速移除,而堆则需要进行更复杂的操作来维护堆的性质。因此,在需要频繁更新区间最大值的场景中,使用单调队列是更优的选择。
🦆
在题解中提到,如果队头元素的`nums`值是正的,则将其加到`nums[i]`上,这是否意味着如果队头元素的`nums`值为负或零则不进行加法操作?这样做的逻辑依据是什么?
确实如此,如果队头元素的`nums`值为负或零,则不进行加法操作。逻辑依据在于我们在寻找以`nums[i]`结尾的最大子序列和时,若前面的最大和为负或零,那么加上这个值不会使`nums[i]`增大,反而可能变小。因此,在这种情况下,选择不加入任何前面的子序列和(即使`nums[i]`单独作为一个子序列),是为了确保不减少`nums[i]`的值,从而有助于求得最大子序列和。
🦆
题解中提到移除超出范围k的元素索引,这里的移除操作是如何确保算法的正确性的?即如何保证移除的总是正确的索引?
在单调队列的实现中,每次处理一个新的元素`nums[i]`时,会首先检查队列头部的索引是否已经超出了当前元素索引`i`与最大距离`k`的范围。即检查队头的索引是否小于`i - k`。由于队列是按照索引顺序从小到大排列的,当队头的索引小于`i - k`时,表示这个索引已经不在允许的距离范围内,因此可以安全地将其移除。这个过程是在每次迭代时进行的,确保了只有当前距离范围内的索引被保留在队列中,从而保证了算法的正确性。

相关问题