题目链接:和至少为 K 的最短子数组
题目描述:
给定一个数组,数组元素的值范围为 32 32 32位整型的范围,同时给定一个 k k k,找出和大于等于 k k k的最短非空子序列(即要求连续),返回这个最短子序列的长度,如果不存在则返回 − 1 -1 −1。
题解:
我们可以依次考虑以某个位置 i i i结尾的子序列,对于位置 i i i我们记 s u m [ i ] sum[i] sum[i]表示下标属于 [ 0 − i ] [0-i] [0−i]的所有元素的和,如果我们要求以 i i i位置结尾的最短子序列(该子序列而和大于等于 k k k),就等价于找到一个尽可能大的下标 p p p使得 s u m [ i ] − s u m [ p − 1 ] ≥ k sum[i] - sum[p-1] \ge k sum[i]−sum[p−1]≥k,我们对不等式进行变形可以得到 s u m [ p − 1 ] ≤ s u m [ i ] − k sum[p-1] \le sum[i] - k sum[p−1]≤sum[i]−k,这说明我们需要维护 s u m [ p − 1 ] sum[p-1] sum[p−1],而这里实际上可以使用单调栈维护,这是因为:如果一个下标 p ′ > p 且 s u m [ p ′ − 1 ] < s u m [ p − 1 ] p' > p且sum[p'-1] < sum[p-1] p′>p且sum[p′−1]<sum[p−1](同时需要注意 p ′ l e i p' le i p′lei),那么 s u m [ p − 1 ] sum[p-1] sum[p−1]这条信息就没有用了,因为 s u m [ p ′ − 1 ] sum[p'-1] sum[p′−1]更小(这代表肯定满足上面的不等式)而且 p ′ p' p′更大(这代表序列会更短)这样的话有用的# s u m [ p − 1 ] sum[p-1] sum[p−1]信息是单调递增的,这样在查找的时候可以使用二分,这样复杂度就能从 O ( n 2 ) O(n^2) O(n2)优化到 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)。
O ( n ) O(n) O(n)的算法:上面的过程实际上还可以优化,在遍历这个单调序列的时候,我们实际上可以不需要二分,而可以直接对单调序列进行遍历,这是因为对于 i i i位置,如果该序列里面的某一个元素满足 s u m [ p − 1 ] ≤ s u m [ i ] − k sum[p-1] \le sum[i] - k sum[p−1]≤sum[i]−k,对于 i ′ > i i' > i i′>i位置来说 s u m [ p − 1 ] sum[p-1] sum[p−1]是没有用的,因为如果此时依然满足 s u m [ p − 1 ] ≤ s u m [ i ‘ ] − k sum[p-1] \le sum[i‘] -k sum[p−1]≤sum[i‘]−k长度 i ′ − p + 1 > i − p + 1 i' - p + 1 > i - p + 1 i′−p+1>i−p+1如果不满足该等式,那更不会被更新;如果一开始不满足 s u m [ p − 1 ] ≤ s u m p [ i ] − k sum[p-1] \le sump[i] - k sum[p−1]≤sump[i]−k,那么该数据就应该被保留下来。所以在遍历的过程中,每遍历一个元素如果满足了不等式就可以删除,这样该单调序列的每个元素会被访问常数次,整个数组会被遍历一次,复杂度是 O ( n ) O(n) O(n)。
代码:
class Solution {
public:
int shortestSubarray(vector<int>& nums, int k) {
int n = nums.size();
int ans = -1;
s.resize(n + 1);
int l = 0, r = 0; // 信息的有效区间
long long sum = 0; // 前缀和
s[r++] = {0, -1};
for (int i = 0; i < n; i++) {
sum += nums[i];
bool move = false;
while(l < r && s[l].first <= sum - k) {
l++;
move = true;
};
if (move) { // 移动意味着存在着满足要求的答案
if (ans == -1) { ans = i - s[l - 1].second; }
else { ans = min(ans, i - s[l - 1].second); }
}
while(l < r && s[r - 1].first >= sum) { r--; }
s[r++] = {sum, i};
}
return ans;
}
private:
// 用pair<long long, int>来保存信息
// first表示某个位置的前缀和
// second表示这个位置
vector<pair<long long, int>> s;
};