问题描述
在小C的班级里,有 N 个学生,每个学生的成绩是 A_i。小C发现了一件有趣的事:当且仅当某个学生的成绩小于或等于自己的有更多人时,这个学生会说谎。换句话说,如果分数小于等于他的学生数量大于比他分数高的学生数量,则他会说谎。
现在,小C想知道班里有多少个学生会说谎。
题目解析
1.分析题目逻
说谎者的核心条件是对比小于等于他的学生数量和比他分数高的学生数量
对于某个学生 A[i]
小于等于 A[i] 的人数:所有分数小于等于 A[i] 的学生数量
大于 A[i] 的人数:班级总人数减去小于等于 A[i] 的人数
如果小于等于的数量大于大于的数量,那么该学生就是“说谎者”
2. 算法思路
排序: 将成绩数组按升序排序,方便统计每个分数小于等于的学生人数。
二分查找: 使用二分查找快速确定小于等于某个分数的学生数量
遍历统计: 遍历每个学生,分别计算小于等于和大于的学生数量,并判断是否满足“说谎者”条件。
代码实现(JAVA)
import java.util.Arrays;
public class Main {
public static int solution(int[] A) {
int n = A.length;
int[] sorted = Arrays.copyOf(A, n);
// 对成绩进行排序
Arrays.sort(sorted);
int count = 0; // 统计说谎者数量
for (int i = 0; i < n; i++) {
// 找到成绩 A[i] 的小于等于数量和大于数量
int lessOrEqual = countLessOrEqual(sorted, A[i]);
int greater = n - lessOrEqual;
// 判断是否满足说谎者的条件
if (lessOrEqual > greater) {
count++;
}
}
return count;
}
// 统计小于等于目标值的数量
private static int countLessOrEqual(int[] sorted, int target) {
int low = 0, high = sorted.length - 1;
while (low <= high) {
int mid = (low + high) / 2;
if (sorted[mid] <= target) {
low = mid + 1; // 移动到右半部分
} else {
high = mid - 1; // 移动到左半部分
}
}
return low; // low 指向第一个大于 target 的位置,因此小于等于 target 的数量是 low
}
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);
}
}
代码详解
1. 主逻辑部分:
Arrays.copyOf(A, n):拷贝成绩数组,避免修改原数组。
Arrays.sort(sorted):对数组按升序排序,为后续统计分布提供便利。
遍历数组中的每个学生分数,调用 countLessOrEqual 方法计算小于等于当前分数的人数,再计算大于当前分数的人数。
2. 二分查找部分:
countLessOrEqual 方法使用二分查找确定目标分数的范围:
如果当前分数sorted[mid] <= target,则移动low
如果当前分数sorted[mid] > target,则移动high
查找完成后,low 指向第一个大于目标分数的位置,因此返回值直接表示小于等于的数量。
3. 返回结果
遍历所有学生统计满足“说谎者”条件的人数并返回
个人思考与分析
1、本题目的难点在于如何高效统计小于等于某个分数的学生人数。暴力解法需要 O(n2) 的复杂度,二分查找将其优化到O(logn),显著提高了性能
2、通过这道题,我深刻体会到对问题分解和建模的重要性。将问题转化为“排序 + 统计”的思路大大简化了解题过程,也提升了代码的可维护性