
思路:前缀和+单调队列
- 连续子区间立即推前缀和,然后要找最短和最大的子区间用单调队列比较方便;
- 某个子区间的和就是两个前缀和sum[i]的差,即∑i=lrnums[i]=sum[r+1]−sum[l];
- 然后利用一个双端队列【后续操作推定】来遍历计算每个子区间的和【每两个前缀和之间的差】:
- 逐个遍历计算合法的子区间,为降低复杂度需将冗余队头、队尾元素去除,那么什么是冗余的呢;
- 对于队头元素,若sumcur−sumhead>=k,那么cur和head两个就组成了最短的合法子数组,cur后续元素都无法再构成一个更短的数组,所以可以将当前的队头弹出;
- 对于队尾元素,若sumcur<sumtail【此时cur还未入队】,那么后续元素x若满足x−sumtail>=k,那么也必然满足x−sumcur>=k,显然cur构成的子数组更短,所以队尾可以弹出;
- 过程中维护最小的结果,同时维护待遍历的双端队列。
Java
两次循环
class Solution {
public int shortestSubarray(int[] nums, int k) {
int n = nums.length, res = n + 1;
var sum = new long[n + 1];
for (int i = 0; i < n; i++)
sum[i + 1] = sum[i] + nums[i];
var dq = new ArrayDeque<Integer>();
for (int i = 0; i <= n; i++) {
while (!dq.isEmpty() && sum[i] - sum[dq.peekFirst()] >= k)
res = Math.min(res, i - dq.pollFirst());
while (!dq.isEmpty() && sum[dq.peekLast()] >= sum[i])
dq.pollLast();
dq.addLast(i);
}
return res > n ? -1 : res;
}
}
一次循环
- 同时计算前缀和与待遍历元素,将前缀和作为双端队列的一部分
class Solution {
public int shortestSubarray(int[] nums, int k) {
int res = Integer.MAX_VALUE;
var dq = new ArrayDeque<Pair<Long, Integer>>();
dq.addLast(new Pair<>(0L, -1));
long cur = 0L;
for (int i = 0; i < nums.length; i++) {
cur += nums[i];
while (!dq.isEmpty() && cur - dq.peekFirst().getKey() >= k)
res = Math.min(res, i - dq.pollFirst().getValue());
while (!dq.isEmpty() && dq.peekLast().getKey() >= cur)
dq.pollLast();
dq.addLast(new Pair<>(cur, i));
}
return res == Integer.MAX_VALUE ? -1 : res;
}
}
- 时间复杂度:O(n)
- 空间复杂度:O(n)
var类型
- 学习参考链接
- 可以说是一个用于简化代码的类型,定义双端队列这种结构时就不需要两边都写一遍了。
- 它可以根据后面的赋值推断类型,也就意味着后面必须有初值。
C++
两次循环
class Solution {
public:
int shortestSubarray(vector<int>& nums, int k) {
int n = nums.size(), res = n + 1;
long sum[n + 1];
sum[0] = 0L;
for (int i = 0; i < n; i++)
sum[i + 1] = sum[i] + nums[i];
deque<int> dq;
for (int i = 0; i <= n; i++) {
while (!dq.empty() && sum[i] - sum[dq.front()] >= k) {
res = min(res, i - dq.front());
dq.pop_front();
}
while (!dq.empty() && sum[dq.back()] >= sum[i])
dq.pop_back();
dq.push_back(i);
}
return res > n ? -1 : res;
}
};
一次循环
class Solution {
public:
int shortestSubarray(vector<int>& nums, int k) {
int res = INT_MAX;
deque<pair<long, int>> dq;
dq.emplace_back(0L, -1);
long cur = 0L;
for (int i = 0; i < nums.size(); i++) {
cur += nums[i];
while (!dq.empty() && cur - dq.front().first >= k) {
res = min(res, i - dq.front().second);
dq.pop_front();
}
while (!dq.empty() && dq.back().first >= cur)
dq.pop_back();
dq.emplace_back(cur, i);
}
return res == INT_MAX ? -1 : res;
}
};
- 时间复杂度:O(n)
- 空间复杂度:O(n)
Rust
两次循环
impl Solution {
pub fn shortest_subarray(nums: Vec<i32>, k: i32) -> i32 {
let n = nums.len();
let mut res = n + 1;
let mut sum = vec![0; n + 1];
for i in 0..n {
sum[i + 1] = sum[i] + nums[i] as i64;
}
let mut dq = std::collections::VecDeque::new();
for i in 0..=n {
while let Some(&cur) = dq.front() {
if sum[i] - sum[cur] >= k as i64 {
res = res.min(i - cur);
dq.pop_front();
}
else {
break;
}
}
while let Some(&cur) = dq.back() {
if sum[cur] >= sum[i] {
dq.pop_back();
}
else {
break;
}
}
dq.push_back(i);
}
if res == n + 1 {
-1
}
else {
res as i32
}
}
}
- 时间复杂度:O(n)
- 空间复杂度:O(n)
总结
- 今天三叶姐姐的方法太复杂了、就短暂地变个心
- rust的数据结构还没学到,所以一次循环就懒了
- 想出来了一半的思路,优化没想到队头的弹出,继续加油~