青训营X豆包MarsCode 技术训练营第一课 | 豆包MarsCode AI 刷题
主题:统计班级中的说谎者
一、问题背景
在一个班级中,某学生会“说谎”的条件是:分数小于或等于他的人数超过分数比他高的人数。即,当一个学生认为自己是“多数派”时,却实际上违反逻辑地进行了判断。
问题的核心在于对分数分布进行统计,并根据给定条件判断哪些学生满足“说谎者”的定义。
二、题目分析
输入说明
- 学生人数 NNN,范围为 1 ≤ NNN ≤ 100。
- 分数数组 AAA 的每个元素 AiA_iAi,范围为 0 ≤ AiA_iAi ≤ 100。
输出说明
输出班级中满足“说谎”条件的学生数量。
核心逻辑
-
对于每个学生 iii:
- 计算 分数小于或等于 AiA_iAi 的学生数量。
- 计算 分数大于 AiA_iAi 的学生数量。
- 比较两者,如果前者大于后者,则该学生是“说谎者”。
三、解题思路
核心:判断每个学生的分数与班级整体分数分布的关系,并找到满足条件的学生数量。步骤:
1. 统计分数的频率
由于题目中分数的范围是 0 到 100,用一个数组 frequency 来记录每个分数出现的次数,快速获得某个分数有多少人。
2. 构建前缀和数组
利用前缀和思想,构建一个数组 prefixSum,使得 prefixSum[i] 表示分数小于等于 iii 的学生数量。前缀和的构建可以大幅优化统计分数范围的操作。
3. 遍历判断
对于每个学生:
- 计算分数小于等于 AiA_iAi 的学生人数
lessOrEqualCount,可以通过prefixSum[A_i]获得。 - 计算分数比他高的学生人数
greaterCount,等于班级总人数减去lessOrEqualCount。 - 如果
lessOrEqualCount > greaterCount,则这个学生会说谎。
4. 统计结果
将所有会说谎的学生人数累加,最终输出结果。
四、代码详解
以下是我的完整的 Java 实现代码:
public static int solution(int[] A) {
int n = A.length;
int[] frequency = new int[101]; // 分数范围是 0 到 100
int[] prefixSum = new int[101];
// 统计每个分数的频率
for (int score : A) {
frequency[score]++;
}
// 构建前缀和数组
prefixSum[0] = frequency[0];
for (int i = 1; i <= 100; i++) {
prefixSum[i] = prefixSum[i - 1] + frequency[i];
}
int liarCount = 0;
// 判断每个学生是否满足说谎的条件
for (int score : A) {
int lessOrEqualCount = prefixSum[score];
int greaterCount = n - lessOrEqualCount;
if (lessOrEqualCount > greaterCount) {
liarCount++;
}
}
return liarCount;
}
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);
}
}
代码解读
-
统计频率
- 通过遍历分数数组 AAA,统计每个分数 AiA_iAi 的出现次数,存入
frequency数组。
- 通过遍历分数数组 AAA,统计每个分数 AiA_iAi 的出现次数,存入
-
构建前缀和
- 利用累加的方式构建
prefixSum,使得可以快速查询分数范围内的学生人数。
- 利用累加的方式构建
-
逻辑判断
- 对每个学生的分数,利用前缀和计算
lessOrEqualCount和greaterCount,判断是否满足说谎条件。
- 对每个学生的分数,利用前缀和计算
时间复杂度
- 构建频率数组:O(N)
- 构建前缀和数组:O(M),其中 MMM 为分数范围(101)。
- 遍历判断:O(N)
- 总时间复杂度:O(N + M),即 O(N)。
- 空间复杂度:O(M),即 O(101)。
五、思路图解
以下是解题的可视化过程:
-
输入分数:
A=[19,27,73,55,88]A = [19, 27, 73, 55, 88]A=[19,27,73,55,88]
-
统计频率和前缀和:
frequency:统计每个分数出现的次数。 frequency[19]=1, frequency[27]=1, …frequency[19] = 1, \ frequency[27] = 1, \ \ldotsfrequency[19]=1, frequency[27]=1, …prefixSum:计算前缀和。 prefixSum[19]=1, prefixSum[27]=2, …prefixSum[19] = 1, \ prefixSum[27] = 2, \ \ldotsprefixSum[19]=1, prefixSum[27]=2, …
-
判断每个学生是否说谎:
- 对每个学生的分数,计算
lessOrEqualCount和greaterCount,并进行比较。
- 对每个学生的分数,计算
六、测试用例
七、一些值得记录的Debug
- 略分数范围导致算法复杂度过高 直接用嵌套循环来比较每个学生和其他所有学生的分数。这会导致时间复杂度 O(N2)O(N^2)O(N2),当 N=100N = 100N=100 时仍然可行,但对于更大的数据量会显著拖慢运行效率。
解决方法: 利用题目给出的分数范围 0≤Ai≤1000 \leq A_i \leq 1000≤Ai≤100,预处理分数频率和前缀和数组,通过常数时间复杂度的查表操作快速完成统计。避免直接比较分数。
2.相等分数时没有正确处理 当有多个学生的分数相同时,错误地将“分数比他高的人数”计算为 0.
if (frequency[score] > greaterCount) { // 错误的比较 liarCount++; }
解决方法:
考虑相等分数的学生人数(即 frequency[score])对统计的影响:
- 如果 lessOrEqualCount>greaterCountlessOrEqualCount > greaterCountlessOrEqualCount>greaterCount,相等分数的学生均满足说谎条件。
八、总结
该题是以分数的统计和条件判断为核心,充分利用了数组和前缀和的高效性。 在这道题的解题过程中, 深刻体会到了移到算法题,竟然可以收获到之前不懂的知识点:
- 如何统计数据频率并高效分析分布。
- 前缀和的实际应用。
- 面对约束范围时的优化思路。