题解:统计班级中会说谎的学生数量
问题描述
给定一个包含 个学生成绩的数组 ,我们需要判断班级中有多少个学生会“说谎”。
一个学生会说谎的条件是:
成绩小于等于他的学生人数多于成绩大于他的学生人数。
输入:
一个整数数组 ,表示 个学生的成绩。
输出:
一个整数,表示会说谎的学生数量。
分析与思路
-
核心问题转化:
- 对于每个学生的分数 ,需要快速统计:
- 小于等于 的学生数量 。
- 大于 的学生数量 。
- 判断 是否成立。
- 对于每个学生的分数 ,需要快速统计:
-
效率优化:
- 暴力统计每个学生的 和 会导致 的复杂度,难以处理较大的输入。
- 我们可以通过排序和二分查找来加速计算。
-
具体步骤:
- 排序数组: 对分数数组 进行排序,方便二分查找。
- 二分查找边界: 使用二分查找快速找到每个分数 的位置 :
- 小于等于 的数量 。
- 大于 的数量 。
- 判断 时,计数器 增加。
-
边界问题:
- 二分查找的实现需要确保边界条件正确:
- 查找时处理等于的情况,避免漏算分数相等的学生。
- 确保最终返回的索引准确表示“最后一个小于等于当前分数的位置”。
- 二分查找的实现需要确保边界条件正确:
二分查找边界分析
在二分查找实现中:
- 初始范围是 ,即 ,。
- 中间位置计算为 。
- 根据当前值与目标值的比较调整范围:
- 如果当前值等于目标值 ,我们继续向右探索(),以确保找到最后一个小于等于目标值的位置。
- 如果当前值大于目标值 ,缩小右边界()。
- 如果当前值小于目标值 ,继续探索右半部分()。
- 最终返回的是 ,表示最后一个小于等于目标值的位置。
完整代码实现
public class Main {
// 冒泡排序
public static void bubbleSort(int arr[]) {
int temp; // 临时变量
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
// 二分查找,返回最后一个小于等于目标值的位置
public static int bSearch(int arr[], int t) {
int l = 0;
int r = arr.length;
while (l < r) {
int mid = l + (r - l) / 2;
if (arr[mid] == t) {
l = mid + 1; // 继续向右探索
} else if (arr[mid] > t) {
r = mid; // 缩小右边界
} else { // arr[mid] < t
l = mid + 1;
}
}
return r - 1;
}
// 主解函数
public static int solution(int[] A) {
// 排序
bubbleSort(A);
int count = 0;
for (int i = 0; i < A.length; i++) {
// 查找分数 A[i] 的最后一个位置
int index = bSearch(A, A[i]);
int le = index + 1; // 小于等于 A[i] 的数量
int gt = A.length - le; // 大于 A[i] 的数量
if (le > gt) {
count++;
}
}
return count;
}
public static void main(String[] args) {
// 测试用例
System.out.println(solution(new int[]{100, 100, 100}) == 3);
System.out.println(solution(new int[]{2, 1, 3}) == 2);
System.out.println(solution(new int[]{30, 1, 30, 30}) == 3);
System.out.println(solution(new int[]{19, 27, 73, 55, 88}) == 3);
System.out.println(solution(new int[]{19, 27, 73, 55, 88, 88, 2, 17, 22}) == 5);
}
}
总结
-
二分查找的边界问题:
- 使用 时,需要特别注意返回值是否正确。
- 表示的范围边界需要适配查找目标,避免索引越界或结果不完整。
-
算法复杂度:
- 排序复杂度为 (冒泡排序),可以优化为 (如使用快速排序)。
- 查找复杂度为 (每次查找复杂度为 )。
-
优化方向:
- 可以替换排序算法以提升性能。
- 在代码实现中,二分查找的边界问题需要特别注意,避免逻辑错误。