统计班级中的说谎者 | 豆包MarsCode AI刷题

103 阅读3分钟

题解:统计班级中会说谎的学生数量

问题描述

给定一个包含 NN 个学生成绩的数组 AA,我们需要判断班级中有多少个学生会“说谎”。
一个学生会说谎的条件是:

成绩小于等于他的学生人数多于成绩大于他的学生人数。

输入:
一个整数数组 AA,表示 NN 个学生的成绩。

输出:
一个整数,表示会说谎的学生数量。


分析与思路

  1. 核心问题转化:

    • 对于每个学生的分数 A[i]A[i],需要快速统计:
      • 小于等于 A[i]A[i] 的学生数量 lele
      • 大于 A[i]A[i] 的学生数量 gtgt
    • 判断 le>gtle > gt 是否成立。
  2. 效率优化:

    • 暴力统计每个学生的 lelegtgt 会导致 O(N2)O(N^2) 的复杂度,难以处理较大的输入。
    • 我们可以通过排序和二分查找来加速计算。
  3. 具体步骤:

    • 排序数组: 对分数数组 AA 进行排序,方便二分查找。
    • 二分查找边界: 使用二分查找快速找到每个分数 A[i]A[i] 的位置 indexindex
      • 小于等于 A[i]A[i] 的数量 le=index+1le = index + 1
      • 大于 A[i]A[i] 的数量 gt=Nlegt = N - le
    • 判断 le>gtle > gt 时,计数器 countcount 增加。
  4. 边界问题:

    • 二分查找的实现需要确保边界条件正确:
      • 查找时处理等于的情况,避免漏算分数相等的学生。
      • 确保最终返回的索引准确表示“最后一个小于等于当前分数的位置”。

二分查找边界分析

在二分查找实现中:

  • 初始范围是 [l,r)[l, r),即 l=0l = 0r=A.lengthr = A.length
  • 中间位置计算为 mid=l+(rl)/2mid = l + (r - l) / 2
  • 根据当前值与目标值的比较调整范围:
    • 如果当前值等于目标值 A[mid]==tA[mid] == t,我们继续向右探索(l=mid+1l = mid + 1),以确保找到最后一个小于等于目标值的位置。
    • 如果当前值大于目标值 A[mid]>tA[mid] > t,缩小右边界(r=midr = mid)。
    • 如果当前值小于目标值 A[mid]<tA[mid] < t,继续探索右半部分(l=mid+1l = mid + 1)。
  • 最终返回的是 r1r - 1,表示最后一个小于等于目标值的位置。

完整代码实现

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);
    }
}

总结

  1. 二分查找的边界问题:

    • 使用 l=mid+1l = mid + 1 时,需要特别注意返回值是否正确。
    • rr 表示的范围边界需要适配查找目标,避免索引越界或结果不完整。
  2. 算法复杂度:

    • 排序复杂度为 O(N2)O(N^2)(冒泡排序),可以优化为 O(NlogN)O(N \log N)(如使用快速排序)。
    • 查找复杂度为 O(NlogN)O(N \log N)(每次查找复杂度为 O(logN)O(\log N))。
  3. 优化方向:

    • 可以替换排序算法以提升性能。
    • 在代码实现中,二分查找的边界问题需要特别注意,避免逻辑错误。