【Java】日志统计[第九届蓝桥杯2018](尺取法/窗口滑动)

题目

题目描述
小明维护着一个程序员论坛。现在他收集了一份"点赞"日志,日志共有N行。
其中每一行的格式是:ts id。表示在ts时刻编号id的帖子收到一个"赞"。
现在小明想统计有哪些帖子曾经是"热帖"。
如果一个帖子曾在任意一个长度为D的时间段内收到不少于K个赞,小明就认为这个帖子曾是"热帖"。
具体来说,如果存在某个时刻T满足该帖在[T, T+D)这段时间内(注意是左闭右开区间)收到不少于K个赞,该帖就曾是"热帖"。
给定日志,请你帮助小明统计出所有曾是"热帖"的帖子编号。
输入
第一行包含三个整数N、D和K。
以下N行每行一条日志,包含两个整数ts和id。
1 <= K <= N <= 100000 0 <= ts <= 100000 0 <= id <= 100000
输出
按从小到大的顺序输出热帖id。每个id一行。
样例输入

7 10 2  
0 1  
0 10    
10 10  
10 1  
9 1
100 3  
100 3 

样例输出
1
3

代码

方法一:

我也不太懂这种是什么方法…
思路就是把读入的数据都放入一个类里,然后按id大小排序,其次再按ts大小排序,然后用下标i和i+k-1进行相关数据的判断

为什么是i+k-1呢?因为我们排序就把就把相同的id放在一块了,这样只要找i的第k个就可以满足赞不少于k个了。比如i=1时,i+k-1=2,id=1,他们的时间差小于d,但是前面i=0时id=1已经满足条件输出了,flag[id=1]被标记为1,所以就继续循环,判断i=2和i+k-1=3…

            |--|
              i=1  i+k-1
               |--|
                  |--|
        i   0  1  2  3   4    5   6 
        id  1  1  1  3   3    10  10
        ts  0  9  10 100 100  0   10
package 日志统计;

import java.util.Arrays;
import java.util.Scanner;

public class Main {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Scanner in = new Scanner(System.in);
        int n = in.nextInt();
        int d = in.nextInt();//时间段
        int k = in.nextInt();//不少于k个赞
        Order order[] = new Order[n]; 
        int flag[] = new int[100005];//标记是否已经满足过条件【避免重复输出】
        for(int i=0;i<n;i++) {
        	order[i] = new Order(in.nextInt(),in.nextInt());
        }
        Arrays.sort(order);
        /**
         * 排序好的order:
         *        i  i+k-1
         *        |--|
         *           i  i+k-1
         *           |--|
         *              ...
         *    i   0  1  2  3   4    5   6 
         *    id  1  1  1  3   3    10  10
         *    ts  0  9  10 100 100  0   10
         * 
         */
        for(int i=0;i<n;i++) {
        	/**
        	 * i+k-1表示第k个的id,
        	 * 
        	 * i+k-1<n 防止数组越界
        	 * 
        	 * order[i].id==order[i+k-1].id 看第i个和第i+k-1个的id是否相同
        	 * 
        	 * order[i+k-1].ts-order[i].ts<d  看第i个和第i+k-1个之间的ts差有没有超出给出的时间范围d
        	 * 
        	 * flag[order[i].id]==0  检查第i个数据的id有没有已经满足条件输出过
        	 */
        	if(i+k-1<n&&order[i].id==order[i+k-1].id&&order[i+k-1].ts-order[i].ts<d&&flag[order[i].id]==0) {
        		System.out.println(order[i].id);
        		flag[order[i].id] = 1;
        	}
        }
		in.close();
	}

}

class Order implements Comparable<Order>{
	int ts;
	int id;
	public Order(int ts, int id) {
		this.ts = ts;
		this.id = id;
	}
	
	public int compareTo(Order o) {//先按id从小到大排序,如果相同就按ts从小到大排序
		if(this.id==o.id) {
			return this.ts-o.ts;
		}
		return this.id-o.id;
	}
}

方法二:双指针/尺取法/窗口滑动

这个是按ts从小到大排序,思路在代码里
看不懂代码可以看看下面这个

 /**
	      *  ①第一次循环right=0  
	      *      l&r
	      *   i   0   1   2   3   4   5    6
	      *   ts  0   0   9   10  10  100  100
	      *   id  1   10  1   10  1    3    3          
	      *   count[1]=1;
	      *            ^
	      *            
	      *  ② 第二次循环right=1
	      *       l   r
	      *   i   0   1   2   3   4   5    6
	      *   ts  0   0   9   10  10  100  100
	      *   id  1   10  1   10  1    3    3   
	      *   count[1]=1;
	      *   count[10]=1;
	      *             ^
	      *             
	      *   ③第三次循环right=2          
	      *       l       r
	      *   i   0   1   2   3   4   5    6
	      *   ts  0   0   9   10  10  100  100
	      *   id  1   10  1   10  1    3    3  
	      *   count[1]=1+1;
	      *              ^
	      *   count[10]=1;
	      *   输出id=1(满足条件)并做标记
	      *   
	      *   ④第四次循环right=3
	      *       l           r
	      *   i   0   1   2   3   4   5    6
	      *   ts  0   0   9   10  10  100  100
	      *   id  1   10  1   10  1    3    3   
	      *   count[1]=1+1;
	      *   count[10]=1+1;
	      *   这时时间差等于d,开始移动left,进入while循环
	      *   
	      *           l       r
	      *   i   0   1   2   3   4   5    6
	      *   ts  0   0   9   10  10  100  100
	      *   id  1   10  1   10  1    3    3
	      *   count[1]=1+1-1;
	      *                ^
	      *   count[10]=1+1;
	      *   这时时间差还是等于d,继续移动left
	      *   
	      *               l   r
	      *   i   0   1   2   3   4   5    6
	      *   ts  0   0   9   10  10  100  100
	      *   id  1   10  1   10  1    3    3
	      *   
	      *   count[1]=1+1-1;
	      *   count[10]=1+1-1;
	      *                 ^
	      *   这时时间差小于d,跳出while循环
	      *  
	      *   ⑤第五次循环right=4
	      *               l       r
	      *   i   0   1   2   3   4   5    6
	      *   ts  0   0   9   10  10  100  100
	      *   id  1   10  1   10  1    3    3
	      *   
	      *   count[1]=1+1-1-1+1;
	      *                    ^
	      *   count[10]=1+1-1;
	      *      ......
	      *   
	      */
package 日志统计;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Set;

public class Main4 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		 Scanner in = new Scanner(System.in);
		 int n = in.nextInt();
	     int d = in.nextInt();//时间段
	     int k = in.nextInt();//不少于k个赞
	     Node node[] = new Node[n];
	     int flag[] = new int[100005];
	     int count[] = new int[100005];
	     for(int i=0;i<n;i++) {
	    	 node[i] = new Node(in.nextInt(),in.nextInt());
	     }
	     Arrays.sort(node);
	     int id;
	     for(int left=0,right=0;right<n;right++) {//这就像一个区间[legt,right],移动right使区间不断扩大
	    	 id = node[right].id;
	    	 count[id]++; 
	    	 
	    	 while(node[right].ts-node[left].ts>=d) { //当node[left]和node[right]之间的时间差大于等于d时,就移动left缩小区间,并把不再在[legt,right]范围里id的点赞数消去
	    		 count[node[left].id]--;
	    		 left++;
	    	 }
	    	 if(count[id]>=k&&flag[id]==0) {//大于等于k个赞并没有输出过
	    		 System.out.println(id);
	    		 flag[id] = 1;
	    	 }
	     }
	}

}
class Node implements Comparable<Node>{
	int ts;
	int id;
	public Node(int ts, int id) {
		this.ts = ts;
		this.id = id;
	}
	
	public int compareTo(Node o) {//按ts从小到大排序
			return this.ts-o.ts;
	}


}

参考博客

方法一

方法二

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值