题目是LeetCode第186场周赛的第四题,链接:带限制的子序列和。具体描述为:给你一个整数数组nums
和一个整数k
,请你返回非空子序列元素和的最大值,子序列需要满足:子序列中每两个相邻的整数nums[i]
和nums[j]
,它们在原数组中的下标i
和j
满足i < j
且j - i <= k
。数组的子序列定义为:将数组中的若干个数字删除(可以删除 0 个数字),剩下的数字按照原本的顺序排布。
1 <= k <= nums.length <= 10^5
-10^4 <= nums[i] <= 10^4
示例1:
输入:nums = [10,2,-10,5,20], k = 2
输出:37
解释:子序列为 [10, 2, 5, 20] 。
示例2:
输入:nums = [-1,-2,-3], k = 1
输出:-1
解释:子序列必须是非空的,所以我们选择最大的数字。
示例3:
输入:nums = [10,-2,-10,-5,20], k = 2
输出:23
解释:子序列为 [10, -2, -5, 20] 。
像这种求子序列最大值的一般都是动态规划的题目,这里也不出意外。假设dp[i]
代表数组nums
以nums[i]
为子序列最后一个元素的非空子序列元素和,则递推公式也比较容易得到:
d
p
[
i
]
=
m
a
x
(
n
u
m
s
[
i
]
,
n
u
m
s
[
i
]
+
m
a
x
(
d
p
[
i
−
j
]
)
)
,
j
∈
[
1
,
k
]
dp[i]=max(nums[i], nums[i]+max(dp[i-j])),j\in[1,k]
dp[i]=max(nums[i],nums[i]+max(dp[i−j])),j∈[1,k],也就是说当前的dp[i]
跟前面k个状态(dp[i-k]~dp[i-1]
)有关,如果前面的k
个状态的元素和都是小于0,那很明显当前的dp[i]
就只能取nums[i]
了,否则的话就是等于nums[i]
加上前面k
个状态的元素和最大者。为了求前面k
个状态的最大者,最简单的方法就是暴力遍历,然后不出意外的就超时了,所以这里必须做优化。这不就是上一篇博客讲到的求滑动窗口中最大值么,所以还是可以用单调队列来解决这个问题。时间复杂度为
O
(
n
)
O(n)
O(n),空间复杂度为
O
(
n
)
O(n)
O(n)。
JAVA版代码如下:
class Solution {
public int constrainedSubsetSum(int[] nums, int k) {
int N = nums.length;
int[] dp = new int[N];
dp[0] = nums[0];
int result = dp[0];
Deque<Integer> deque = new LinkedList<>();
deque.offerLast(0);
for (int i = 1; i < N; ++i) {
dp[i] = nums[i];
if (dp[deque.peekFirst()] > 0) {
dp[i] = nums[i] + dp[deque.peekFirst()];
}
while (deque.size() > 0 && dp[deque.peekLast()] < dp[i]) {
deque.pollLast();
}
deque.offerLast(i);
if (i - deque.peekFirst() >= k) {
deque.pollFirst();
}
result = Math.max(result, dp[i]);
}
return result;
}
}
提交结果如下:
![](https://img-blog.csdnimg.cn/20200430144841139.png)
为了加速,可以用数组代替双端队列如下。
JAVA版代码如下:
class Solution {
public int constrainedSubsetSum(int[] nums, int k) {
int N = nums.length;
int[] dp = new int[N];
dp[0] = nums[0];
int result = dp[0];
int[] deque = new int[N];
deque[0] = 0;
int left = 0, right = 0;
for (int i = 1; i < N; ++i) {
dp[i] = nums[i];
if (dp[deque[left]] > 0) {
dp[i] = nums[i] + dp[deque[left]];
}
while (right - left >= 0 && dp[deque[right]] < dp[i]) {
--right;
}
deque[++right] = i;
if (i - deque[left] >= k) {
++left;
}
result = Math.max(result, dp[i]);
}
return result;
}
}
提交结果如下:
![](https://img-blog.csdnimg.cn/20200430145140366.png)
Python版代码如下:
class Solution:
def constrainedSubsetSum(self, nums: List[int], k: int) -> int:
N = len(nums)
deque = collections.deque()
dp = [0] * N
dp[0] = nums[0]
result = dp[0]
deque.append(0)
for i in range(1, N):
dp[i] = nums[i] + max(0, dp[deque[0]])
while len(deque) > 0 and dp[deque[-1]] < dp[i]:
deque.pop()
deque.append(i)
if i - deque[0] >= k:
deque.popleft()
result = max(result, dp[i])
return result
提交结果如下:
![](https://img-blog.csdnimg.cn/20200430171013762.png)