日志统计问题( 错误示范 + 双指针 + 滑动窗口 )

日志统计问题

文章目录

  • 日志统计问题
    • 前言
    • 题目描述
    • 问题分析
      • 方法选取
    • 错误示范
    • 正确解法
    • 总结

前言

很多小伙伴在练习算法题目的时候经常碰上这样一类题目,给出 nts , id 表示在某时刻对某 id 进行了某某操作,但随着时间的流逝,该操作带来的影响会逐步消退,让我们统计在某个区间段内达到要求的 id
这里还可以看往期的类似文章
外卖优先级问题: https://blog.csdn.net/2302_77698668/article/details/132641348

题目描述

小明维护着一个程序员论坛。现在他收集了一份”点赞”日志,日志共有 N 行。

其中每一行的格式是:
ts id 表示在 ts 时刻编号 id 的帖子收到一个”赞”。
现在小明想统计有哪些帖子曾经是”热帖”。
如果一个帖子曾在任意一个长度为 D 的时间段内收到不少于 K个赞,小明就认为这个帖子曾是”热帖”。

具体来说,如果存在某个时刻 T 满足该帖在 [T,T+D) 这段时间内(注意是左闭右开区间)收到不少于 K个赞,该帖就曾是”热帖”。
给定日志,请你帮助小明统计出所有曾是”热帖”的帖子编号。

输入格式
第一行包含三个整数 N,D,K。

以下 N 行每行一条日志,包含两个整数 ts 和 id。

输出格式
按从小到大的顺序输出热帖 id。
每个 id 占一行。
数据范围
1≤K≤N≤105,
0≤ts,id≤105,
1≤D≤10000
输入样例:
7 10 2
0 1
0 10
10 10
10 1
9 1
100 3
100 3
输出样例:
1
3

问题分析

方法选取

对于这样的题目,如果采取循环一遍输入数据,再循环一遍每个编号的热度,最后进行统计,这样的时间复杂度是 O(n),也就是我们常说的暴力解法。
而对于这类题目,关键就在于在每个编号的上一次更新时间和当前更新时间上面相下文章,这里就需要用到我们的双指针算法了。

如果一个帖子曾在任意一个长度为 D 的时间段内收到不少于 K个赞,小明就认为这个帖子曾是”热帖”。

这里有一个长度为D的时间段窗口,不知道大家看到这个联想到了什么—》滑动窗口,而滑动窗口的实现同样依赖于双指针算法

错误示范

大家先看代码:这里的代码通过了 9/14个数据,是我一开始使用的方法,大家可以找找错误的思路。

#include
#include
using namespace std;
const int N =1e5 + 7;
int n,d,k;
bool flag[N];//记录那些达标的编号
struct Node{//这个结构体存储的是输入的数据
    int t,id;
    bool operator <(const Node&W)const
    //结构体重载排序的方法,使得数组按照标准的字典序排序
    {
        return t<W.t;
    }
}nodes[N];

struct Tie{//存储的是每个帖子
    
    int cnt;//点赞的数目
    int last=0;//上次被点赞的时间
}T[N];
int main(){
    int n,d,k;
    cin>>n>>d>>k;
    for(int i=0;i<n;i++){
        int t,id;
        cin>>t>>id;
        // t++;id++;
        nodes[i]={t,id};
    }
    sort(nodes,nodes+n);
    //记得一定要排序
    for(int i=0;i<n;i++){//对按时间排序的数据进行处理
        int t=nodes[i].t;
        int id=nodes[i].id;
        int last=T[id].last;
        if(t< last +d){//如果在时间窗口的范围内
            T[id].cnt++;
            if(T[id].cnt>=k){
                flag[id]=true;
            }
        }
        else{//超出时间范围
            T[id].cnt=1;
        }
        T[id].last=t;//更新上次得分的时间
    }
    //循环输出达标的编号
    for(int i=0;i<N;i++){
        if(flag[i]){
            cout<<i<<endl;
        }
    }
}

细心的小伙伴可以发现错误的地方是

if(t< last +d){//如果在时间窗口的范围内
            T[id].cnt++;
            if(T[id].cnt>=k){
                flag[id]=true;
            }
        }
        else{//超出时间范围
            T[id].cnt=1;
        }

问题出现在 cnt[ id ] 的更新上面
这里忽略了滑动窗口带来的影响,所以代码设计的思路存在错误

正确解法

小伙伴们可以和之前错误的代码进行对比,看看思路上面有哪里不同。

#include
#include
using namespace std;
const int N =1e5 + 7;
typedef pair<int,int> PII;
int n,d,k;
PII nodes[N];
int cnt[N];
bool st[N];
int main(){
    cin>>n>>d>>k;
    for(int i=0;i<n;i++){
        cin>>nodes[i].first>>nodes[i].second;
    }
    sort(nodes,nodes+n);
    //在这上面的思路都和之前一样
    //----------------------
    //下面是滑动窗口 i 是右端 j 是左端
    for(int i=0,j=0;i<n;i++){
        int id=nodes[i].second;
        cnt[id]++;
        //下面是滑动窗口的具体实现手段
        while(nodes[i].first-nodes[j].first>=d){
        //当窗口末尾的 j 点退出了窗口
            cnt[nodes[j].second]--;
            //对 j 点记载的信息进行消除
            j++;//更新窗口左端
        }
        if(cnt[id]>=k) st[id]=true;
    }
    for(int i=0;i<100000;i++){
        if(st[i]) cout<<i<<endl;
    }
}

总结

希望小伙伴们吸取到我错误示范中的教训,滑动窗口对数组更新时带来的影响在实现上面要慎重,以及学会如何构建滑动窗口以及更新滑动窗口。

你可能感兴趣的:(双指针,算法,数据结构,蓝桥杯,c++)