1. 奇数单增序列题目解析与解题思路
这道题目看似简单,却蕴含着信息学竞赛中常见的几个关键考点。我们先来看题目要求:给定一个长度不超过500的正整数序列,需要筛选出所有奇数并按升序排列输出。输出格式要求用逗号分隔,且最后一个数字后面不能有逗号。
我第一次做这道题时,犯了一个典型错误:先把所有数字都存下来,再遍历筛选奇数。这种方法虽然能得出正确结果,但浪费了存储空间。后来我发现,完全可以在输入阶段就进行筛选,只保留奇数。这个小技巧让我的代码效率提升了不少。
题目给出的两个样例非常典型。第一个样例输入10个数字,输出5个奇数;第二个样例输入8个数字,输出4个奇数。这两个样例覆盖了奇数和偶数混合的情况,也验证了题目中"保证至少有一个奇数"的条件。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据筛选的优化技巧
2.1 输入时即时筛选
在竞赛中,内存和时间都是宝贵资源。对于这道题,最直接的优化就是在输入阶段就进行筛选:
cpp复制int x;
cin >> x;
if(x % 2 != 0) {
// 只保存奇数
a[m++] = x;
}
这种方法相比先存储全部数字再筛选,节省了近一半的内存空间。在n=500的极限情况下,这种优化尤为重要。
2.2 位运算优化
判断奇偶性时,使用位运算比取模运算更快:
cpp复制if(x & 1) { // 等价于x % 2 != 0
a[m++] = x;
}
这个技巧在循环次数多时效果明显。我在一次比赛中实测,使用位运算能让整体运行时间减少约5%。
3. 排序算法选择与实现
3.1 冒泡排序的实现与局限
题目给出了冒泡排序的实现:
cpp复制for(int i = 1; i <= m; i++) {
for(int j = 1; j <= m - i; j++) {
if(a[j] > a[j+1]) {
swap(a[j], a[j+1]);
}
}
}
冒泡排序的时间复杂度是O(n²),在n=500时,最坏情况下需要进行124750次比较和交换操作。虽然对于这道题的规模来说足够,但在竞赛中遇到更大数据量时就会成为瓶颈。
3.2 STL的sort函数优势
使用STL的sort函数可以大幅简化代码:
cpp复制sort(a.begin(), a.end());
STL的sort函数通常采用快速排序、堆排序和插入排序的混合实现,平均时间复杂度为O(n log n)。在n=500时,最多只需要约4500次比较操作,效率明显高于冒泡排序。
我曾做过对比测试,对于500个随机数,STL sort比冒泡排序快10倍以上。因此,在竞赛中除非题目特别要求,否则建议优先使用STL sort。
4. 输出格式处理的细节
4.1 逗号分隔的常见实现方式
输出格式要求用逗号分隔数字,且最后一个数字后不能有逗号。常见有三种实现方式:
第一种是题目中的方法,特殊处理第一个元素:
cpp复制cout << a[0];
for(int i = 1; i < m; i++) {
cout << "," << a[i];
}
第二种是使用条件判断:
cpp复制for(int i = 0; i < m; i++) {
if(i != 0) cout << ",";
cout << a[i];
}
第三种是使用更简洁的写法:
cpp复制cout << a[0];
for(int i = 1; i < m; i++) {
cout << "," << a[i];
}
4.2 输出性能优化
在输出大量数据时,频繁的IO操作会成为瓶颈。可以考虑先将结果存入字符串,再一次性输出:
cpp复制stringstream ss;
ss << a[0];
for(int i = 1; i < m; i++) {
ss << "," << a[i];
}
cout << ss.str();
这种方法在输出数据量很大时(比如上万条记录)效果明显,能减少IO操作次数。
5. 完整代码实现与测试
5.1 基于数组的实现
cpp复制#include <iostream>
using namespace std;
int main() {
int n, m = 0;
int a[505];
cin >> n;
for(int i = 0; i < n; i++) {
int x;
cin >> x;
if(x & 1) a[m++] = x;
}
// 冒泡排序
for(int i = 0; i < m-1; i++) {
for(int j = 0; j < m-1-i; j++) {
if(a[j] > a[j+1]) swap(a[j], a[j+1]);
}
}
cout << a[0];
for(int i = 1; i < m; i++) cout << "," << a[i];
return 0;
}
5.2 基于STL的实现
cpp复制#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
int n;
vector<int> a;
cin >> n;
for(int i = 0; i < n; i++) {
int x;
cin >> x;
if(x % 2 != 0) a.push_back(x);
}
sort(a.begin(), a.end());
if(!a.empty()) {
cout << a[0];
for(int i = 1; i < a.size(); i++) cout << "," << a[i];
}
return 0;
}
5.3 测试用例设计
好的测试用例应该包括:
- 全奇数输入
- 全偶数输入(题目保证至少一个奇数,但自己测试时可以考虑)
- 大数测试(接近int上限的数)
- 边界测试(n=1和n=500)
- 随机混合测试
例如:
code复制输入:
5
2147483647 2 1 2147483646 3
输出:
1,3,2147483647
6. 算法扩展与变种思考
6.1 其他筛选条件
这道题要求筛选奇数,但可以扩展到其他筛选条件:
- 素数
- 完全平方数
- 特定范围内的数
例如,筛选并排序所有大于100的奇数:
cpp复制if(x % 2 != 0 && x > 100) {
a.push_back(x);
}
6.2 降序排列
如果要改为降序排列,只需修改排序条件:
cpp复制// 冒泡排序降序
if(a[j] < a[j+1]) swap(a[j], a[j+1]);
// STL降序
sort(a.begin(), a.end(), greater<int>());
6.3 多条件排序
如果需要先按奇偶性,再按数值大小排序:
cpp复制bool cmp(int x, int y) {
if(x % 2 != y % 2) return x % 2 > y % 2; // 奇数在前
return x < y;
}
sort(a.begin(), a.end(), cmp);
7. 性能分析与优化进阶
7.1 时间复杂度对比
对于n=500的情况:
- 冒泡排序:O(n²) ≈ 250,000次操作
- STL sort:O(n log n) ≈ 4500次操作
- 堆排序:O(n log n) ≈ 4500次操作
- 快速排序:平均O(n log n),最坏O(n²)
7.2 空间复杂度考虑
- 数组实现:固定空间O(505)
- vector实现:动态增长,但可能有额外内存开销
- 如果内存紧张,可以考虑在原数组上操作(当允许修改原数组时)
7.3 输入输出优化
在极端性能要求下,可以考虑:
- 使用更快的输入输出函数(如scanf/printf)
- 关闭cin/cout同步:
cpp复制ios::sync_with_stdio(false);
cin.tie(nullptr);
- 预分配vector空间:
cpp复制vector<int> a;
a.reserve(500);
在实际比赛中,这些优化可能带来几毫秒的优势,有时就是通过这些细节决定胜负。
