找到最大非递减数组的长度

题目

2945. 找到最大非递减数组的长度


给你一个下标从 0 开始的整数数组 nums 。

你可以执行任意次操作。每次操作中,你需要选择一个 子数组 ,并将这个子数组用它所包含元素的  替换。比方说,给定数组是 [1,3,5,6] ,你可以选择子数组 [3,5] ,用子数组的和 8 替换掉子数组,然后数组会变为 [1,8,6] 。

请你返回执行任意次操作以后,可以得到的 最长非递减 数组的长度。

子数组 指的是一个数组中一段连续 非空 的元素序列。

示例 1:

1
2
3
4
5
6
7
8
9
输入:nums = [5,2,2]
输出:1
解释:这个长度为 3 的数组不是非递减的。
我们有 2 种方案使数组长度为 2 。
第一种,选择子数组 [2,2] ,对数组执行操作后得到 [5,4] 。
第二种,选择子数组 [5,2] ,对数组执行操作后得到 [7,2] 。
这两种方案中,数组最后都不是 非递减 的,所以不是可行的答案。
如果我们选择子数组 [5,2,2] ,并将它替换为 [9] ,数组变成非递减的。
所以答案为 1 。

示例 2:

1
2
3
输入:nums = [1,2,3,4]
输出:4
解释:数组已经是非递减的。所以答案为 4 。

示例 3:

1
2
3
4
输入:nums = [4,3,2,6]
输出:3
解释:将 [3,2] 替换为 [5] ,得到数组 [4,5,6] ,它是非递减的。
最大可能的答案为 3 。

提示:

  • 1 <= nums.length <= 10^5
  • 1 <= nums[i] <= 10^5

题解

方法一:

思路

动态规划,对于每个前缀nums[0...i],让$f_{i}$表示为该前缀的变为最长非递减数组的长度。$e_{i}$表示为最长非递减数组的最后一个数最小的情况。

也就是说我们不仅要让一个数组变为最长非递减数组,而且还要让这个数组的末尾元素最小化。

设$ps$为前i个数的前缀和。

状态转移$f_i = 1 + \max \limits_{j < i, ps_i-ps_j\ge e_j} f_j$,$f$是非递减的。

为了保证$e_i$尽量小,我们选择比较大的$j$进行转移

对于$j’ < j’’$,均满足$ps_{j’}+e_{j’}\le ps_i$,$ps_{j’’}+e_{j’’}\le ps_i$

当$ps_{j’}+e_{j’} \ge ps_{j’’}+e_{j’’}$,由于$ps_{j’} < ps_{j’’}$,所以$e_{j’}>e_{j’’}$,我们优先选择$j’’$作为转移位置。我们可以维护一个严格增长的单调队列,队列内元素$i,j,(i<j)$满足$ps_{i}+e_{i} < ps_{j}+e_{j}$

在求$f_i$前,如果移除队首元素后,新的队首元素x,仍然满足$ps_{x}+e_{x} \le ps_{i}$,那么不断移除队首知道违背此条件。这样做为了让队首元素尽量大,这样得到$e_i$会尽量小。

在求得$f_i$后,弹出队尾元素维护队列单调。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public:
int findMaximumLength(vector<int>& nums) {
int n = nums.size();
vector<long> ps(n+1), e(n+1), f(n+1), dq(n+1);
for (int i=1; i<=n; i++) {
ps[i] = ps[i-1] + nums[i-1];
}
int l = 0, r = 1;
for (int i=1; i<=n; i++) {
while (l+1<r && e[dq[l+1]]<=ps[i]-ps[dq[l+1]]) {
l++;
}
e[i] = ps[i]-ps[dq[l]];
f[i] = f[dq[l]]+1;
while (l<r && e[dq[r-1]]+ps[dq[r-1]] >= e[i]+ps[i]) r--;
dq[r++] = i;
}
return f[n];
}
};