~/blog/posts

cat max-subinterval-analysis.md|

题目分析:寻找满足题意的最大子区间

【牛客:空调遥控】

题目链接:https://www.nowcoder.com/practice/7cb58d56b30c4280ac07ae4428022e03

知识点:双指针、二分

输入:6 2
      1 5 3 2 4 6
输出:5

说明:温度调成3或4,都可以满足5名队员同时进入训练状态


当看见这一题后,可能会直接使用暴力枚举的方法。

暴力枚举写法

#include <bits/stdc++.h>
using namespace std;

bool Satisfy(int i, int k, int p){
    if(abs(i-k)<=p){
        return true;
    }
    return false;
}

int main() {
    int n,p;
    cin>>n>>p;
    vector<int> a(n);
    for(int i=0; i<n; i++){
        cin>>a[i];
    }

    sort(a.begin(),a.end());

    int best = 0;
    for(int k=a[0]; k<=a[n-1]; k++){
        int count = 0;
        for(int x:a){
            if(Satisfy(x,k,p)){
                count++;
            }
        }
        best = max(best,count);
    }
    cout<<best;
}

但是由于数据量过大,暴力枚举的时间复杂度为 O(n²),在数据量过大时会导致 TLE,所以需要对现有代码进行优化。


题目分析

原命题:存在一个实数中心 k,使得对于子数组中的任意元素 x,都满足:k - p <= x <= k + p

易得转化命题:对于子数组中的最小值 min_val 和最大值 max_val,满足:max_val - min_val <= 2 * p

所以就可以转化为求序列中满足条件的最大子序列。

所以,可以使用双指针写法,寻找到满足 max_val - min_val <= 2 * p 的最大子序列,然后求得其中元素个数即可。

双指针/滑动窗口写法

#include <bits/stdc++.h>
using namespace std;

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);

    int n,p;
    cin>>n>>p;
    vector<int> a(n);
    for(int i=0;i<n;i++){
        cin>>a[i];
    }

    sort(a.begin(),a.end());

    int best = 0;
    for(int right=0 ,left=0; right<n; right++){   // 让右指针 i 从 0 遍历到 n-1

        while(left<=right && (a[right] - a[left]) > p*2){     //while 循环:收缩左边界
            // 如果窗口不合法,就将左指针 left 向右移动一位,缩小窗口
            left++;
        }
        //此时,窗口 [left, right] 是以 right 为右边界的、满足条件的最长合法窗口
        best = max(best, right-left+1);
    }
    cout<<best;
}

使用双指针,时间复杂度为 O(n log n),满足题目要求。


二分写法

当然,使用二分法也可以快速寻找到最大子序列。

#include <bits/stdc++.h>
using namespace std;

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, p;
    cin >> n >> p;
    vector<int> a(n);
    for (int i = 0; i < n; i++) {
        cin >> a[i];
    }

    sort(a.begin(), a.end());

    int best = 0;
    for (int i = 0; i < n; i++) {
        // 计算右边界最大允许值
        int right_limit = a[i] + 2 * p;

        // 使用 upper_bound 找到第一个大于 right_limit 的位置
        auto it = upper_bound(a.begin(), a.end(), right_limit);

        // 第四步:计算当前窗口的长度
        // [a.begin() + i, it) 这个区间内的所有元素都满足条件
        // 元素个数 = it - (a.begin() + i) = (it - a.begin()) - i
        int current_count = (int)(it - a.begin()) - i;

        // 第五步:更新全局最大值
        best = max(best, current_count);

    }
    cout << best;
}

时间复杂度同样为 O(n log n),满足题目要求。


总结

综上所述,面对有序的序列时,可以考虑使用双指针或二分进行优化。