个人题解|统计班级中的说谎者

123 阅读5分钟

问题描述

在小C的班级里,有 N 个学生,每个学生的成绩是 A_i。小C发现了一件有趣的事:当且仅当某个学生的成绩小于或等于自己的有更多人时,这个学生会说谎。换句话说,如果分数小于等于他的学生数量大于比他分数高的学生数量,则他会说谎。

现在,小C想知道班里有多少个学生会说谎。

代码思路:

  1. 问题背景: 这道题的目标是,给定一个学生成绩的列表,我们需要判断每个学生是否会“说谎”。学生的判断标准是:

    • 每个学生声称自己有比自己分数低或相等的学生数。
    • 如果该学生实际比自己分数高的学生数更多,那么他就是在“说谎”。 换句话说,某个学生会说谎的条件是:他自己分数小于或等于的学生数超过了比他分数高的学生数。
  2. 核心思路

    • 计数每个成绩出现的次数:首先,我们通过统计每个成绩的频率,得到每个成绩出现了多少次。这样做的目的是为了快速计算比某个分数小或等于的学生人数。
    • 前缀和的使用:通过前缀和,我们可以快速计算出成绩小于等于某个值的学生数量。前缀和是对一个数组的累加结果,可以帮助我们在 O(1) 时间内得到区间和。通过这个前缀和数组,我们可以在常数时间内查询任何成绩小于等于某个分数的学生数量。
    • 判断是否说谎:每个学生的说谎判断条件是:如果某个成绩小于等于该成绩的学生数量大于比该成绩高的学生数量,那么该学生会说谎。

具体步骤:

  1. 统计成绩频率: 通过一个 count 数组记录每个成绩出现的次数。假设成绩范围是 [0, 100](根据实际情况可能不同),count[i] 表示成绩 i 的出现次数。

  2. 构造前缀和数组: 使用一个前缀和数组 pre 来记录小于等于某个成绩的学生数量。数组 pre[i] 表示成绩小于或等于 i 的学生数量。

    • 前缀和的构造pre[i] 等于 pre[i-1] + count[i]。这个累加的过程会将前面所有成绩的学生数量加在一起,使得 pre[i] 包含了成绩 i 及其以下所有成绩的学生数量。
  3. 判断每个学生是否说谎: 对于每个学生的成绩 A[i],我们可以通过 pre[A[i]] 计算小于等于 A[i] 的学生数量,然后通过总学生数 N 减去这个值,得到比 A[i] 高的学生数量。如果 pre[A[i]] 大于 N - pre[A[i]],说明该学生会说谎。

  4. 输出结果: 最后,返回说谎学生的数量。

代码实现

def solution(A):
    N = len(A)
    
    # 假设成绩范围在[0, 100],如果范围不同可以调整
    max_score = max(A)
    
    # 创建计数数组,记录每个成绩出现的次数
    count = [0] * (max_score + 1)
    
    # 统计每个成绩的出现次数
    for score in A:
        count[score] += 1
    
    # 创建前缀和数组,pre[i]表示成绩小于等于i的学生数量
    pre = [0] * (max_score + 1)
    pre[0] = count[0]
    
    for i in range(1, max_score + 1):
        pre[i] = pre[i - 1] + count[i]
    
    # 计算会说谎的学生数量
    liar_count = 0
    for score in A:
        # 小于等于score的学生数量
        less_equal_count = pre[score]
        # 比score高的学生数量
        greater_count = N - less_equal_count
        if less_equal_count > greater_count:
            liar_count += 1
    print("res = ",liar_count)        
    return liar_count

# 输入测试

if __name__ == "__main__":
    # Add your test cases here
    print(solution([100, 100, 100]) == 3)
    print(solution([2, 1, 3]) == 2)
    print(solution([30, 1, 30, 30]) == 3)
    print(solution([19, 27, 73, 55, 88]) == 3)
    print(solution([19, 27, 73, 55, 88, 88, 2, 17, 22]) == 5)

关键点讲解:

  1. 计数数组 count

    • count[i] 记录成绩为 i 的学生数量。通过这个数组,我们能够知道每个成绩的频次。
  2. 前缀和数组 pre

    • pre[i] 是前缀和数组,记录成绩小于或等于 i 的学生数量。它的计算方式是:pre[i] = pre[i-1] + count[i]。通过前缀和数组,我们可以快速计算出某个成绩以下的学生数量。
  3. 判断学生是否会说谎

    • 对于每个学生的成绩 A[i],我们先通过前缀和数组得到小于等于 A[i] 的学生数量 pre[A[i]]。然后,通过总学生数 N 减去这个值,得到比该成绩高的学生数量。最后,通过条件 pre[A[i]] > N - pre[A[i]] 判断该学生是否说谎。

前缀和的介绍

前缀和(Prefix Sum)是一种用于高效查询区间和的技术。它的核心思想是,构造一个新的数组,使得这个数组中的每个元素表示原数组中从第一个元素到当前元素的累加和。前缀和能够将求区间和的时间复杂度从 O(N) 降低到 O(1),从而大大提高效率。

前缀和的构造:

假设我们有一个数组 arr,长度为 n,其前缀和数组 prefix 是通过如下方式计算的:

  • prefix[0] = arr[0]
  • prefix[i] = prefix[i-1] + arr[i] (对于 i > 0

通过构造前缀和数组,我们可以快速计算任意区间 [l, r] 的和:

  • 区间和 = prefix[r] - prefix[l-1]

这使得区间和的计算时间复杂度降为 O(1),而前缀和数组的构造时间复杂度是 O(n)

总结:

通过利用 计数排序前缀和 技巧,我们能够高效地解决这个问题。前缀和是一个强大的工具,它可以将许多需要重复计算的区间和问题转化为常数时间查询,从而提高算法的效率。