📌  相关文章
📜  子序列可能的最大和,使得在数组中的距离 < K 处没有两个元素出现(1)

📅  最后修改于: 2023-12-03 15:25:01.934000             🧑  作者: Mango

子序列可能的最大和,使得在数组中的距离 < K 处没有两个元素出现

在给定的数组中,找到一个子序列,使得这个子序列的和最大,同时保证子序列中的相邻元素的下标距离小于K。

问题描述

假设我们有一个数组nums,要求从中找到一个子序列,使得这个子序列的元素之和最大。但是,这个子序列中任意相邻的元素之间的下标差不能超过K。也就是说,如果在这个子序列中有两个元素的下标之差大于K,那么这个子序列就不符合要求。

解决方案
动态规划

我们可以使用动态规划来解决这个问题。设$dp[i]$表示在前$i$个元素中符合要求的最大子序列和,那么我们可以得到如下的状态转移方程:

$$ dp[i] = \begin{cases} \max(dp[j]) + nums[i] & j<i \text{且} i-j \leq K \ nums[i] & i \leq K \end{cases} $$

其中,$dp[j]$表示前$j$个元素中符合要求的最大子序列和,即$dp[j]$所表示的子序列中,任意相邻的元素之间的下标差都不大于K。

边界条件:$dp[i]=nums[i]$,当$i\leq K$时。

最终的答案即为$\max\limits_{i=1}^{n} dp[i]$。

这个方法的时间复杂度是$O(n^2)$,空间复杂度也是$O(n^2)$。我们可以进行状态压缩,将空间复杂度优化到$O(n)$。

滑动窗口+单调队列

我们还可以使用滑动窗口和单调队列来解决这个问题。具体来说,我们维护一个长度为K的窗口,窗口中的元素之和为$sum$。然后我们用一个单调队列来维护前K个元素中最大的元素,和次大的元素,以及它们出现的位置。

当我们往右滑动窗口时,我们需要将窗口两端的元素加入或移出队列。为了保证队列中只保留窗口内的元素,我们还需要检查队列首端元素是否已经不在窗口内,如果不在窗口内,则将其移除队列。

这个方法的时间复杂度是$O(n)$,空间复杂度也是$O(n)$。

代码示例
动态规划
def max_subarray(nums, k):
    n = len(nums)
    dp = [0] * n
    dp[0] = nums[0]
    for i in range(1, n):
        dp[i] = nums[i]
        for j in range(i-1, max(i-k-1, -1), -1):
            dp[i] = max(dp[i], dp[j] + nums[i])
    return max(dp)
滑动窗口+单调队列
from collections import deque
def max_subarray(nums, k):
    q = deque()
    n = len(nums)
    sum = 0
    ans = float('-inf')
    for i in range(n):
        sum += nums[i]
        while q and q[0][1] <= i-k:
            sum -= q.popleft()[0]
        while q and q[-1][0] <= nums[i]:
            sum -= q.pop()[0]
        q.append((nums[i], i))
        ans = max(ans, sum)
    return ans

以上两种方法的时间复杂度和空间复杂度不同,具体选择哪种方法,需要根据具体情况来选择。