【每日一题】滑动窗口题解

滑动窗口

https://ac.nowcoder.com/acm/problem/50528

常规做法当然是单调队列,但我全都要。

单调队列

Solution

求最大值和最小值的方法实际上是一样的。

这里只以求最大值为例:

从左往右扫到某一个值的时候,如果当前值的大小比前面的某些值要大的时候,结尾是当前位置往右的的最大值就不可能是前面比当前值小的。

还有一种情况是队头已经超出这个区间范围了,这个时候就需要先把他给剔除。

于是构造一个单调队列,队头是值较大的一端,而队尾是值较小的一端,那么构造好每一个位置的单调队列后,单调队列的队头就是这个区间的最大值。

时间复杂度 空间复杂度

Code

#include<bits/stdc++.h>
using namespace std;
vector<int> calc(vector<int> a, int k, bool isMin) {
  const int n = a.size();
  // 如果是求最小值,取负数做一遍求最大值的,然后再将结果取负数即可
  if(isMin) for(int &x: a) x = -x;
  deque<int> q; // 双端队列
  vector<int> ret(n - k + 1); // 返回的数组
  for(int i = 0; i < n; i++) {
    while(!q.empty() && q.front() <= i - k) q.pop_front(); // 下标超出区间
    // 构造出单调队列 将没有机会成为最大值的出队
    while(!q.empty() && a[q.back()] < a[i]) q.pop_back();
    q.push_back(i);
    // 队头作为最大值
    if(i + 1 >= k) ret[i + 1 - k] = a[q.front()];
  }
  if(isMin) for(int &x: ret) x = -x;
  return ret;
}
int main() {
  ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
  int n, k; cin >> n >> k;
  vector<int> a(n);
  for(int &x: a) cin >> x;
  vector<int> res = calc(a, k, true); // 计算各区间最小值
  for(int i = 0; i < n - k + 1; i++) {
    cout << res[i] << " \n"[i == n - k];
  }
  res = calc(a, k, false); // 计算各区间最大值
  for(int i = 0; i < n - k + 1; i++) {
    cout << res[i] << " \n"[i == n - k];
  }
}

ST表

Solution

ST表可以预处理数组,然后可以询问每一个区间内的最大值或者是最小值。

那么就可以很轻松地MLE这一道题了。

怎么卡我内存啊!!算了一下发现确实不行。

下面代码只过了50%。

时间复杂度 空间复杂度

Code

#include<bits/stdc++.h>
using namespace std;
// 建ST表
template<typename T>
struct ST {
  vector<vector<pair<T, T>>> dp;
  vector<int> lg;
  // 预处理 O(nlog(n))
  ST(const vector<T> &a) {
    const int n = a.size(), m = 33 - __builtin_clz(n);
    // 申请足够的空间 需要的空间有点多
    dp = vector<vector<pair<T, T>>>(n + 1, vector<pair<T, T>>(m));
    lg = vector<int> (n + 1, 0);
    for(int i = 1; i <= n; i++) {
      dp[i][0] = {a[i - 1], a[i - 1]}, lg[i] = lg[i >> 1] + 1;
    }
    for(int j = 1; (1 << j) <= n; j++) {
      for(int i = 1; i + (1 << j) - 1 <= n; i++) {
        pair<T, T> &x = dp[i][j - 1], &y = dp[i + (1 << (j - 1))][j - 1];
        dp[i][j] = {min(x.first, y.first), max(x.second, y.second)};
      }
    }
  }
  // 询问 O(1)
  pair<T, T> minmax(int l, int r) {
    l++, r++;
    int k = lg[r - l + 1] - 1;
    return {
      min(dp[l][k].first, dp[r - (1 << k) + 1][k].first), 
      max(dp[l][k].second, dp[r - (1 << k) + 1][k].second)
    };
  }
};
int main() {
  ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
  int n, k; cin >> n >> k;
  vector<int> v(n);
  for(int &x: v) cin >> x;
  ST<int> obj(v);
  vector<int> a1(n - k + 1), a2(n - k + 1);
  for(int i = k - 1; i < n; i++) {
    tie(a1[i - k + 1], a2[i - k + 1]) = obj.minmax(i - k + 1, i);
  }
  for(int i = 0; i < n - k + 1; i++) {
    cout << a1[i] << " \n"[i == n - k];
  }
  for(int i = 0; i < n - k + 1; i++) {
    cout << a2[i] << " \n"[i == n - k];
  }
}

线段树

Solution

线段树就很强大了,相比于ST表还可以支持修改内存也只是,但是各种操作的复杂度都是的而不是的。

建完线段树暴力就完事了。

可恶啊,最小值和最大值一起求还是会MLE。那只能分开来求,而且分开来求的话就不需要把答案存起来。

时间差点超了但是能过就行嘻嘻。

时间复杂度 空间复杂度

Code

#include<bits/stdc++.h>
using namespace std;
#define lson i << 1, l, mid
#define rson i << 1 | 1, mid + 1, r
#define mid ((l + r) >> 1)
const int N = 1000000 + 10;
int b[N << 2], a[N];
// 线段树建树
void build(int i, int l, int r, bool is) {
  if(l == r) { b[i] = a[l]; return; }
  build(lson, is); build(rson, is);
  if(is) b[i] = min(b[i << 1], b[i << 1 | 1]);
  else b[i] = max(b[i << 1], b[i << 1 | 1]);
}
// 线段树查询
int query(int i, int l, int r, int L, int R, bool is) {
  // 超出范围返回一个极限值
  if(r < L || l > R) return is ? INT_MAX : INT_MIN;
  if(L <= l && r <= R) return b[i];
  if(is) return min(query(lson, L, R, is), query(rson, L, R, is));
  else return max(query(lson, L, R, is), query(rson, L, R, is));
}
int main() {
  ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
  int n, k; cin >> n >> k;
  for(int i = 1; i <= n; i++) cin >> a[i];
  build(1, 1, n, true);
  for(int i = k; i <= n; i++) {
    cout << query(1, 1, n, i - k + 1, i, true) << " \n"[i == n];
  }
  build(1, 1, n, false); // 省点空间做build两次线段树
  for(int i = k; i <= n; i++) {
    cout << query(1, 1, n, i - k + 1, i, false) << " \n"[i == n];
  }
}
全部评论

相关推荐

快手 测开 月薪大概在40k左右
java转行老兵烧烤:那家测试能给到40k😅😅
点赞 评论 收藏
分享
安菲尔德星期三:63退休只是说63才能领退休金,不代表63还能有工作
点赞 评论 收藏
分享
头像
10-22 19:18
上海大学 后端
bxiajuxujwjqna:水印都叠杀人书了
点赞 评论 收藏
分享
2 收藏 评论
分享
牛客网
牛客企业服务