问题描述
在小C的班级里,有 N 个学生,每个学生的成绩是 A_i。小C发现了一件有趣的事:当且仅当某个学生的成绩小于或等于自己的有更多人时,这个学生会说谎。换句话说,如果分数小于等于他的学生数量大于比他分数高的学生数量,则他会说谎。
现在,小C想知道班里有多少个学生会说谎。
代码思路:
-
问题背景: 这道题的目标是,给定一个学生成绩的列表,我们需要判断每个学生是否会“说谎”。学生的判断标准是:
- 每个学生声称自己有比自己分数低或相等的学生数。
- 如果该学生实际比自己分数高的学生数更多,那么他就是在“说谎”。 换句话说,某个学生会说谎的条件是:他自己分数小于或等于的学生数超过了比他分数高的学生数。
-
核心思路:
- 计数每个成绩出现的次数:首先,我们通过统计每个成绩的频率,得到每个成绩出现了多少次。这样做的目的是为了快速计算比某个分数小或等于的学生人数。
- 前缀和的使用:通过前缀和,我们可以快速计算出成绩小于等于某个值的学生数量。前缀和是对一个数组的累加结果,可以帮助我们在
O(1)时间内得到区间和。通过这个前缀和数组,我们可以在常数时间内查询任何成绩小于等于某个分数的学生数量。 - 判断是否说谎:每个学生的说谎判断条件是:如果某个成绩小于等于该成绩的学生数量大于比该成绩高的学生数量,那么该学生会说谎。
具体步骤:
-
统计成绩频率: 通过一个
count数组记录每个成绩出现的次数。假设成绩范围是[0, 100](根据实际情况可能不同),count[i]表示成绩i的出现次数。 -
构造前缀和数组: 使用一个前缀和数组
pre来记录小于等于某个成绩的学生数量。数组pre[i]表示成绩小于或等于i的学生数量。- 前缀和的构造:
pre[i]等于pre[i-1] + count[i]。这个累加的过程会将前面所有成绩的学生数量加在一起,使得pre[i]包含了成绩i及其以下所有成绩的学生数量。
- 前缀和的构造:
-
判断每个学生是否说谎: 对于每个学生的成绩
A[i],我们可以通过pre[A[i]]计算小于等于A[i]的学生数量,然后通过总学生数N减去这个值,得到比A[i]高的学生数量。如果pre[A[i]]大于N - pre[A[i]],说明该学生会说谎。 -
输出结果: 最后,返回说谎学生的数量。
代码实现
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)
关键点讲解:
-
计数数组
count:count[i]记录成绩为i的学生数量。通过这个数组,我们能够知道每个成绩的频次。
-
前缀和数组
pre:pre[i]是前缀和数组,记录成绩小于或等于i的学生数量。它的计算方式是:pre[i] = pre[i-1] + count[i]。通过前缀和数组,我们可以快速计算出某个成绩以下的学生数量。
-
判断学生是否会说谎:
- 对于每个学生的成绩
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)。
总结:
通过利用 计数排序 和 前缀和 技巧,我们能够高效地解决这个问题。前缀和是一个强大的工具,它可以将许多需要重复计算的区间和问题转化为常数时间查询,从而提高算法的效率。