1. 问题分析与算法设计思路
这道题目要求我们处理一个序列,找出第k大的数与第k小的数之差,并判断这个差值是否为质数。作为算法竞赛中的经典题型,它综合考察了排序、数学和边界条件处理能力。
1.1 核心问题拆解
题目可以分解为三个关键子任务:
- 找出序列中第k小的数
- 找出序列中第k大的数
- 计算两者差值并判断是否为质数
对于长度为n的序列,第k小的数就是排序后第k个元素(索引k-1),而第k大的数则是排序后倒数第k个元素(索引n-k)。这种对称关系是解决问题的关键。
1.2 算法选择依据
使用标准库的sort函数是最优选择,原因有三:
- 时间复杂度为O(n log n),对于n≤10000完全够用
- 实现简单,避免手动实现排序的错误风险
- C++的sort在底层使用内省排序(introsort),结合了快速排序、堆排序和插入排序的优点
注意:虽然题目保证数字不超过maxlongint,但差值m可能为负值,而质数定义要求m>1。代码中is_prime函数已正确处理此边界条件。
2. 代码实现详解
2.1 质数判断优化算法
cpp复制bool is_prime(int m) {
if (m <= 1)
return false;
if (m == 2 || m == 3)
return true;
if (m % 2 == 0 || m % 3 == 0)
return false;
int limit = sqrt(m);
for (int i = 5; i <= limit; i += 6) {
if (m % i == 0 || m % (i + 2) == 0)
return false;
}
return true;
}
这个质数判断函数采用了数论中的优化技巧:
- 所有大于3的质数都满足6k±1的形式
- 只需检查到√m即可,因为若m有大于√m的因数,必定对应一个小于√m的因数
- 提前排除2和3的倍数,减少循环次数
实测表明,这种优化比简单试除法快3-5倍。
2.2 主程序逻辑解析
cpp复制int main() {
int n, k;
cin >> n >> k;
vector<int> a(n);
for (int i = 0; i < n; i++) {
cin >> a[i];
}
sort(a.begin(), a.end());
int min_k = a[k - 1]; // 第k小
int max_k = a[n - k]; // 第k大
int m = max_k - min_k;
if (is_prime(m)) {
cout << "YES" << endl;
} else {
cout << "NO" << endl;
}
cout << m << endl;
return 0;
}
关键点说明:
- 使用vector动态数组存储序列,避免固定数组的内存浪费
- sort默认升序排列,符合题目要求
- 索引计算时注意C++从0开始的特点
- 输出格式严格匹配题目要求,包括换行符
3. 边界条件与特殊测试用例
3.1 常见边界情况
| 测试用例类型 | 示例输入 | 预期输出 | 检查重点 |
|---|---|---|---|
| k=1 | 5 1 1 2 3 4 5 |
YES 4 |
最大最小差值 |
| k=n | 5 5 1 1 2 2 3 |
NO 0 |
相同元素处理 |
| 负差值 | 4 2 4 3 2 1 |
NO -2 |
质数定义验证 |
| 大n | 10000 5000 (随机数) |
依数据而定 | 性能测试 |
3.2 易错点警示
- 数组越界:当k>n时未处理(题目已保证0<k≤n)
- 重复元素:如序列有重复值时,第k大/小的定义仍成立
- 质数误判:1和负数不是质数,0也非质数
- 整数溢出:虽然题目保证输入在maxlongint内,但差值仍需用int存储
4. 算法优化与扩展思考
4.1 时间复杂度分析
- 输入读取:O(n)
- 排序:O(n log n)
- 质数判断:O(√m)
整体复杂度由排序步骤决定,为O(n log n)
4.2 可能的优化方向
- 使用nth_element:
cpp复制nth_element(a.begin(), a.begin()+k-1, a.end());
int min_k = a[k-1];
nth_element(a.begin(), a.begin()+n-k, a.end());
int max_k = a[n-k];
这样可以将时间复杂度降到O(n),但实际运行时间可能因常数因子而慢于sort
-
并行计算:对于超大n,可将排序任务分块并行处理
-
预处理质数表:如果题目需要多次查询,可预先用筛法生成质数表
4.3 相关题型扩展
- 求前k小/大的和
- 判断第k小与第k大的比值是否为整数
- 找出所有满足a[i]是第k小的数的i
- 流式数据下的解决方案(无法存储全部数据)
5. 实际编码技巧分享
5.1 调试技巧
- 打印中间结果:
cpp复制cout << "Sorted array: ";
for(int x : a) cout << x << " ";
cout << endl;
- 边界测试数据生成:
python复制import random
n = 10000
k = random.randint(1, n)
print(n, k)
print(' '.join(str(random.randint(0, 1e9)) for _ in range(n)))
5.2 代码风格建议
- 使用更具描述性的变量名:
cpp复制int kth_smallest = a[k - 1];
int kth_largest = a[n - k];
- 添加必要注释:
cpp复制// 注意:质数定义要求m > 1
if (m <= 1) return false;
- 封装功能模块:
cpp复制int find_kth_smallest(const vector<int>& arr, int k) {
vector<int> tmp(arr);
sort(tmp.begin(), tmp.end());
return tmp[k-1];
}
6. 竞赛应用场景
这类题目常出现在编程竞赛的以下环节:
- 初赛基础题:考察基本编码能力
- 团队赛协作题:需要与队友配合完成
- 面试笔试环节:测试基础算法掌握程度
典型变种包括:
- 多维数据下的第K极值
- 带权重的前K项查询
- 动态数据流中的实时查询
在实际工程中,类似思想可用于:
- 数据分析中的异常值检测
- 评分系统的最高/最低分过滤
- 资源调度中的优先级排序
掌握这类基础算法问题,是构建更复杂系统的基础能力。建议通过在线判题系统(如LeetCode、Codeforces)多加练习,培养快速准确实现算法的能力。