《算法竞赛·快冲300题》每日一题:“最小区间”

算法竞赛·快冲300题》将于2024年出版,是《算法竞赛》的辅助练习册。
所有题目放在自建的OJ New Online Judge
用C/C++、Java、Python三种语言给出代码,以中低档题为主,适合入门、进阶。


最小区间” ,链接: http://oj.ecustacm.cn/problem.php?id=1869

题目描述

【题目描述】 给定N头牛的位置P[i]和类别T[i]。区间[L,R]满足包含所有类别的牛。求区间[L,R]最小长度是多少。
【输入格式】 第一行为正整数N,1≤N≤50000。接下来N行,每行两个正整数P[i]和T[i],不超过10^9。
【输出格式】 输出最小长度。
【输入样例】

6
25 7
26 1
15 1
22 3
20 1
30 1

【输出样例】

4

题解

   这是一道典型的尺取法题,用快慢指针扫描区间,计算满足要求的最小区间长度。步骤是:
   (1)按牛的位置排序。
   (2)用窗口[L, R]遍历所有区间,并找到满足要求的区间最小长度。L是慢指针,R是快指针,初始值指向第一头牛。先让R往前走,直到区间[L, R]刚好包含所有类别(总数为type_all)为止,这是一个满足要求的区间。然后让后面的L往前走,直到类别不够type_all为止,这是区间[L, R]不满足要求,下一步再让R往前走,直到再次满足要求。当R走到最后一头牛的位置,L走到最后一个满足要求的位置时,所有满足要求的区间都遍历过了。比较所有满足要求的区间的长度,找到最小长度。
   计算量等于排序加尺取法。排序是O(nlogn)的;尺取法是O(n)的,因为L和R都只走一遍。总复杂度O(nlogn)。
【重点】 尺取法,快慢指针 。

C++代码

   用map统计窗口[L, R]内的类别数量。

#include<bits/stdc++.h>
using namespace std;
int main(){
    
    
    int n;    cin >> n;
    vector<pair<int,int>>a(n, pair<int,int>(0, 0));
    set<int>Type;                             //用set统计有多少类别
    for(int i = 0; i < n; i++){
    
    
        cin >> a[i].first >> a[i].second;
        Type.insert(a[i].second);             //Type.size()是类别总数
    }
    sort(a.begin(), a.end());                 //按first从小到大排序
    int L = 0, R = 0, ans = 1e9;
    int type_all = Type.size(), type_now = 0; //type_all是类别总数,type_now是[L,R]内的类别数量
    unordered_map<int,int> Cnt;               //Cnt统计当前窗口内出现的类别各有多少个
    while(L < n){
    
    
        while(R < n && type_now != type_all) //快指针R一直走,直到窗口包含所有类别
            if(Cnt[a[R++].second]++ == 0)    //统计R位置的类别出现次数,等于0表示没有出现过
                type_now++;                  //窗口内包含的类别数量加1
        if(type_now == type_all)             //满足条件
            ans = min(ans, a[R-1].first - a[L].first);  //计算区间长度,找最小的
        if(--Cnt[a[L++].second] == 0)        //去掉慢指针L位置的类别,然后L往前走一步
            type_now--;                      //如果这个类别的数量减到0,那么type_now减一
    }
    cout<<ans<<endl;
    return 0;
}

Java代码

import java.util.*;
public class Main {
    
    
    public static void main(String[] args) {
    
    
        Scanner input = new Scanner(System.in);
        int n = input.nextInt();
        List<Pair<Integer, Integer>> a = new ArrayList<>();
        Set<Integer> Type = new HashSet<>();       //用set统计有多少类别
        for (int i = 0; i < n; i++) {
    
    
            int first = input.nextInt();
            int second = input.nextInt();
            a.add(new Pair<>(first, second));
            Type.add(second);                     //Type.size()是类别总数
        }
        Collections.sort(a, (x, y) -> {
    
               //按first从小到大排序
            int cmp = x.getFirst().compareTo(y.getFirst());
            if (cmp != 0)  return cmp;
            else           return x.getSecond().compareTo(y.getSecond());
        });
        int L = 0, R = 0, ans = Integer.MAX_VALUE;
        int type_all = Type.size(), type_now = 0; //type_all是类别总数,type_now是[L,R]内的类别数量
        Map<Integer, Integer> Cnt = new HashMap<>();  //Cnt统计当前窗口内出现的类别各有多少个
        while (L < n) {
    
    
            while (R < n && type_now != type_all) {
    
       //快指针R一直走,直到窗口包含所有类别
                if (Cnt.getOrDefault(a.get(R).getSecond(), 0) == 0)  //统计R位置的类别出现次数,等于0表示没有出现过
                    type_now++;             //窗口内包含的类别数量加1
                Cnt.put(a.get(R).getSecond(), Cnt.getOrDefault(a.get(R).getSecond(), 0) + 1);
                R++;
            }
            if (type_now == type_all)            //满足要求
                ans = Math.min(ans, a.get(R - 1).getFirst() - a.get(L).getFirst());  //计算区间长度,找最小的
            Cnt.put(a.get(L).getSecond(), Cnt.getOrDefault(a.get(L).getSecond(), 0) - 1); //去掉慢指针L位置的类别
            if (Cnt.getOrDefault(a.get(L).getSecond(), 0) == 0)    
				type_now--;               //如果这个类别的数量减到0,那么type_now减一
            L++;                          //然后L往前走一步
        }
        System.out.println(ans);
    }
}
class Pair<A, B> {
    
    
    A first;
    B second;
    Pair(A first, B second) {
    
    
        this.first = first;
        this.second = second;
    }
    public A getFirst(){
    
     return this.first; }
    public B getSecond(){
    
    return this.second;}
}

Python代码

  

from collections import defaultdict
def solve(n, a):
    type = set()                                # 用set统计有多少类别
    for i in range(n):   type.add(a[i][1])      # type.size()是类别总数
    a.sort()                                    # 按位置a[i][0]从小到大排序
    L, R = 0, 0
    ans = float("inf")
    type_all, type_now = len(type), 0          # type_all是类别总数,type_now是[L,R]内的类别数量
    cnt = defaultdict(int)                     # cnt统计当前窗口内出现的类别各有多少个
    while L < n:
        while R < n and type_now != type_all:   # 快指针R一直走,直到窗口包含所有类别
            if cnt[a[R][1]] == 0:               # 统计R位置的类别出现次数,等于0表示没有出现过
                type_now += 1                   # 窗口内包含的类别数量加1
            cnt[a[R][1]] += 1
            R += 1
        if type_now == type_all:                # 满足条件
            ans = min(ans, a[R-1][0] - a[L][0]) # 计算区间长度,找最小的
        cnt[a[L][1]] -= 1
        if cnt[a[L][1]] == 0:                   # 去掉慢指针L位置的类别,然后往前走一步
            type_now -= 1                       # 如果这个类别的数量减到0,那么type_now减一
        L += 1
    return ans
if __name__ == "__main__":
    n = int(input())
    a = []
    for i in range(n):  a.append(tuple(map(int, input().split())))
    ans = solve(n, a)
    print(ans)

猜你喜欢

转载自blog.csdn.net/weixin_43914593/article/details/132507378