题面
在小C的班级里,有 N 个学生,每个学生的成绩是 A_i。小C发现了一件有趣的事:当且仅当某个学生的成绩小于或等于自己的有更多人时,这个学生会说谎。换句话说,如果分数小于等于他的学生数量大于比他分数高的学生数量,则他会说谎。现在,小C想知道班里有多少个学生会说谎。
经验
拿到题目之后:
- 第一件事就是理解题目的意思。这一步考验的是阅读理解能力和数学语言转化能力。 最简单的方法就是举一个例子代入题目中的条件中去,用大白话理解,然后用题目中的数字化已知信息对大白话文字进行翻译,进一步进行化简。
- 第二件事就是分条列出解题步骤。这一步对应的是真正写代码的时候的逻辑。梳理好了写代码的时候逻辑链条会更加清晰顺畅。
- 第三件事才是敲代码。不要盲目地一上来就敲代码。要用脑子做题就像要抬头看路一样。
解题思路
这道题的关键在于理解“说谎”的条件:一个学生会说谎当且仅当其成绩小于或等于他的成绩的学生数量大于比他分数高的学生数量。
1. 理解题目条件
对于每个学生 i,假设他的成绩是 A[i],他会说谎的条件是:
- 比
A[i]成绩低或等于的学生人数(包括自己)大于比A[i]成绩高的学生人数。
这个条件可以转化为:
- 条件一:
小于等于A[i]的学生数量 > 比A[i]大成绩的学生数量。
进一步推导:
-
假设总共有
N个学生,那么:- 比
A[i]成绩高的学生数量 =N - 小于等于A[i]的学生数量。
- 比
-
上述条件变成:
小于等于A[i]的学生数量 > (N - 小于等于A[i]的学生数量)。
即:
小于等于A[i]的学生数量 > N / 2。
所以,对于每个学生 i 来说,只要他成绩小于等于的学生数量大于 N/2,他就会说谎。
2. 解题步骤
- 排序:首先,对学生的成绩进行排序。这样可以方便地统计“比某个成绩大的学生数量”和“小于等于某个成绩的学生数量”。
- 遍历每个学生:对于每个学生,计算他成绩小于等于的学生数量,判断是否大于
N / 2来确定是否说谎。
3. 实现步骤
- 排序:将成绩从小到大排序。
- 计算每个学生的“比他成绩高的学生数量”和“小于等于他成绩的学生数量” 。
- 判断:对于每个学生,如果“小于等于他成绩的学生数量”大于
N / 2,则该学生会说谎。 - 输出结果:统计说谎的学生数量。
4.具体实现
def countLyingStudents(A):
# 步骤 1: 排序
A_sorted = sorted(A) N = len(A)
# 步骤 2: 计算并统计说谎的学生
lying_count = 0
# 遍历每个学生的成绩
for score in A:
# 计算小于等于当前学生成绩的数量
less_equal_count = sum(1 for x in A_sorted if x <= score)
# 判断是否说谎
if less_equal_count > N / 2:
lying_count += 1
return lying_count
# 测试样例
A = [100, 100, 100]
print(countLyingStudents(A)) # 输出 3
5. 复杂度分析
- 时间复杂度:排序的时间复杂度是 O(N log N),在遍历每个学生并计算小于等于的学生数量时,如果使用
sum遍历所有学生的成绩,时间复杂度为 O(N)。因此,总时间复杂度为 O(N log N)。 - 空间复杂度:需要 O(N) 的空间来存储排序后的数组。
6. 优化
上述解法虽然可以正确解决问题,但它的时间复杂度为 O(N log N) + O(N^2),可以优化。考虑到我们不需要每次都用 sum 遍历整个数组来计算 less_equal_count,我们可以通过 二分查找 来高效地计算每个学生成绩小于等于的学生数量。
优化方法:
- 使用二分查找来代替
sum,从排序后的数组中快速找到成绩小于等于当前成绩的学生数量。 - 使用
bisect_right来找到成绩小于等于当前学生成绩的位置。
优化后的实现:
import bisect
def countLyingStudents(A):
# 步骤 1: 排序
A_sorted = sorted(A)
N = len(A)
# 步骤 2: 计算并统计说谎的学生
lying_count = 0
# 遍历每个学生的成绩
for score in A:
# 使用二分查找来计算小于等于当前学生成绩的数量
less_equal_count = bisect.bisect_right(A_sorted, score)
# 判断是否说谎
if less_equal_count > N / 2:
lying_count += 1
return lying_count
# 测试样例
A = [100, 100, 100]
print(countLyingStudents(A)) # 输出 3
7. 总结
- 核心思路:通过计算每个学生成绩小于等于的学生数量,比较它是否大于
N / 2来确定是否说谎。 - 时间复杂度优化:通过二分查找(
bisect_right)来替代线性遍历,提高了计算效率。 - 适用场景:本题适合使用排序和二分查找等方法优化计算,尤其在数据量较大的时候,这种方法能显著提高性能。