给你一个整数数组 nums
和两个整数:left
及 right
。找出 nums
中连续、非空且其中最大元素在范围 [left, right]
内的子数组,并返回满足条件的子数组的个数。
生成的测试用例保证结果符合 32-bit 整数范围。
示例 1:
输入: nums = [2,1,4,3], left = 2, right = 3
输出: 3
解释: 满足条件的三个子数组:[2], [2, 1], [3]
示例 2:
输入: nums = [2,9,2,5,6], left = 2, right = 8
输出: 7
提示:
题解
DP 动态规划
给定数组 nums 及区间 [left, right],要求统计连续子数组中其最大值落在区间内的子数组个数。直观暴力的思路是枚举所有子数组,然后判断最大值,但时间复杂度为 O(n²) 或更高,不适合 n 较大的情况。
状态定义: 表示以 结尾的、满足 子数组最大值在 [left, right] 内
的子数组数量。
答案:
状态转移分析
在遍历过程中,我们需要注意三种情况(设上一次遇到不合法(即 > right)的位置为 j):
-
- 当前元素超出上界,任何以其结尾的子数组都必定非法。
- 此时:,并更新 ,表示将合法区间“断开”。
-
- 当前元素本身合法,可以使包含它的子数组满足条件。
- 以 i 为结尾的所有从 j+1 开始的子数组均合法,因此
-
- 当前元素本身不满足条件,但如果前面已经出现过合法元素(即 非 0),延伸这些子数组仍然合法。
- 此时:。
这种递推实际上构成了一种动态规划:
- 当 在 内时,“激活”了合法性,所有从上一次非法位置 后开始的子数组都变合法。
- 当 时,只能延续之前已经合法的子数组。
- 遇到 时,必须断开连续区间。
在实际代码中,我们可以将 dp 数组压缩为一个变量(通常称为 temp),同时用变量 j 记录最后一个不合法的下标。
代码:完整 DP 框架
下面的代码中我们使用一个 dp 数组来保存每个位置的状态,最后将 dp 数组求和得到答案。
class Solution {
public:
int numSubarrayBoundedMax(vector<int>& nums, int left, int right) {
int n = nums.size();
vector<int> dp(n, 0);
int ans = 0;
int last_invalid = -1; // 上一次出现 > right 的位置
for (int i = 0; i < n; i++) {
if (nums[i] > right) {
// 当前元素非法,断开连续区间
last_invalid = i;
dp[i] = 0;
} else if (nums[i] >= left) {
// 当前元素合法,激活连续子数组
dp[i] = i - last_invalid;
} else {
// nums[i] < left,则只能延续前面的状态
dp[i] = (i == 0 ? 0 : dp[i-1]);
}
ans += dp[i];
}
return ans;
}
};
代码:状态压缩版
由于 dp[i]
仅依赖于前一个状态,我们可以用一个变量来保存当前状态,从而实现状态压缩。代码如下:
class Solution {
public:
int numSubarrayBoundedMax(vector<int>& nums, int left, int right) {
int ans = 0, temp = 0, last_invalid = -1;
for (int i = 0; i < nums.size(); i++) {
if (nums[i] > right) {
last_invalid = i;
temp = 0; // 以当前位置结尾没有合法子数组
} else if (nums[i] >= left) {
temp = i - last_invalid; // 当前元素“激活”了合法性
}
// 当 nums[i] < left 时,temp 保持不变(延续之前合法子数组的数量)
ans += temp;
}
return ans;
}
};
这种写法相当于把完整 DP 中 的状态直接压缩到了变量 temp 中。
单调栈 考虑最大值
对于数组中的每个元素,如果该元素满足 ,则它有可能作为某个子数组的最大值。
关键在于如何快速统计以 为最大值的子数组个数。
如果我们能知道:
- 在 左侧,离 最近的且比 大的元素的位置(记为 ),
- 在 右侧,离 最近的且比 大的元素的位置(记为 ),
那么,对于位置 ,其可以“支配”的子数组区间为: - 子数组起点可以在区间 中任取,个数为 ;
- 子数组终点可以在区间 中任取,个数为 。
因此,以 为最大值的子数组个数为:。
注意:
只有当 落在 内时,才能作为合法子数组的最大值:
- 如果 ,则不能单独激活合法性;
- 如果 ,则整个子数组不合法。