《算法竞赛·快冲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)