题目
小蓝老师教的编程课有 N 名学生,编号依次是 1 . . . N。第 i 号学生这学期刷题的数量是 Ai。
对于每一名学生,请你计算他至少还要再刷多少道题,才能使得全班刷题比他多的学生数不超过刷题比他少的学生数。
输入格式
第一行包含一个正整数 N。
第二行包含 N 个整数:A1, A2, A3, . . . , AN.
5
12 10 15 20 6
输出格式
输出 N 个整数,依次表示第 1 . . . N 号学生分别至少还要再刷多少道题。
0 3 0 0 7
题解
可以用暴力,但是在寻找最少刷题数时会超时,
所以可以利用前缀和求出每个刷题数阶段的人数,
当不满足 刷题数多于当前学生刷题数的人数 不超过 刷题数少于当前学生刷题数的人数时
(注意临界点,当当前学生刷题数为0时需特殊处理)
再利用二分法在当前学生刷题数与最大刷题数之间寻找最少刷题数
import java.util.*;
public class LessTopicCount {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();//人数
int m = 100000;//最大刷题数
int [] array = new int[n];//每个人刷题数
int [] sum = new int[100010];//每个刷题数阶段的人数
for( int i=0; i<n; i++)
{
array[i] = scanner.nextInt();
//记录每种刷题数出现的次数,即人数
sum[ array[i] ]++;
}
for( int i=1; i<=m; i++)
{
//前缀和
//记录每个阶段(刷0-i题)刷题数的总人数
sum[i]+=sum[i-1];
}
for( int i=0; i<n; i++)
{
//枚举每个学生的刷题数是否满足
int l = array[i]; //二分查找左值
int r = m; //二分查找右值
if( sum[m]-sum[l] <= sum[Math.max(0, l-1)]) {
//注意边界:最少不能少于0道刷题数
//刷题比他多的人数小于等于刷题比他少的
//那么他就不需要再刷题,直接输出0即可
System.out.print(0+" ");
continue;
}
//二分查找
while( l<r )//终止条件:刷题数多于他的人数 等于 或 小于 (最接近)刷题数比他少的人数(找最中间的那个刷题数)
{
int mid = l+r>>1;
int more = sum[m] - sum[mid];//刷题数大于他的人数
int less = sum[mid-1] -1;//刷题数小于他的人数,减1是因为自己不能算
//没有提前终止的条件,因为可能有多个人的刷题数相同
if( more>less )
{
//说明刷题数大于他的人数 多了,所以刷题数要增加
l = mid+1;
}
else
{
//说明刷题数大于他的人数 少了,所以刷题数要减少
r = mid;
}
}
System.out.print((l - array[i]) + " ");
}
}
}