1. 问题背景与需求分析
PTA(Programming Teaching Assistant)是高校程序设计课程常用的在线评测系统,实验7-1-10"组个最小数"是一道经典的算法练习题。这道题目考察的是对数字排列组合的理解以及贪心算法的应用能力。
题目要求:给定一组0-9的数字(可能包含重复数字),要求用这些数字组成一个最小的整数,且这个整数不能以0开头。例如给定数字[2, 2, 0, 0, 0],可以组成的最小整数是2002而不是0022或0202。
这个问题的实际应用场景包括:
- 生成最小ID号或订单编号
- 资源分配时的最优排序
- 数据压缩中的编码优化
2. 解题思路与算法设计
2.1 核心问题拆解
要解决这个问题,我们需要处理两个关键点:
- 避免前导零:最小的非零数字必须放在首位
- 剩余数字按升序排列:确保整体数值最小
2.2 贪心算法选择
采用贪心算法是最合适的解决方案,因为:
- 局部最优能保证全局最优
- 时间复杂度低(O(n))
- 实现简单直观
算法步骤:
- 统计每个数字的出现次数
- 找到最小的非零数字作为首位
- 剩余数字按从小到大的顺序排列
3. 详细实现与代码解析
3.1 输入处理
首先需要处理输入的数字序列。以C语言为例:
c复制int count[10] = {0}; // 初始化数字计数器
char input[1001];
scanf("%s", input);
// 统计每个数字出现的次数
for(int i = 0; input[i] != '\0'; i++) {
count[input[i]-'0']++;
}
3.2 确定首位数字
找到最小的非零数字作为首位:
c复制int first = 0;
for(int i = 1; i < 10; i++) {
if(count[i] > 0) {
first = i;
count[i]--; // 使用掉一个该数字
break;
}
}
3.3 构建最小数
剩余数字按升序排列:
c复制printf("%d", first); // 输出首位
// 按顺序输出剩余数字
for(int i = 0; i < 10; i++) {
while(count[i] > 0) {
printf("%d", i);
count[i]--;
}
}
4. 完整代码实现
c复制#include <stdio.h>
#include <string.h>
int main() {
int count[10] = {0};
char input[1001];
scanf("%s", input);
// 统计数字出现次数
for(int i = 0; input[i] != '\0'; i++) {
count[input[i]-'0']++;
}
// 找最小非零首位
int first = 0;
for(int i = 1; i < 10; i++) {
if(count[i] > 0) {
first = i;
count[i]--;
break;
}
}
printf("%d", first);
// 输出剩余数字
for(int i = 0; i < 10; i++) {
while(count[i] > 0) {
printf("%d", i);
count[i]--;
}
}
return 0;
}
5. 测试用例与验证
5.1 常规测试用例
| 输入 | 预期输出 | 说明 |
|---|---|---|
| 22000 | 2002 | 含多个0的情况 |
| 123 | 123 | 无重复数字 |
| 55550 | 50555 | 多个相同数字 |
| 100000 | 10000 | 多个0且首位为1 |
5.2 边界测试用例
| 输入 | 预期输出 | 说明 |
|---|---|---|
| 0 | 0 | 只有0的情况 |
| 1 | 1 | 单个数字 |
| 111111 | 111111 | 全相同数字 |
| 9876543210 | 1023456789 | 包含所有数字 |
6. 算法优化与扩展
6.1 时间复杂度分析
当前算法的时间复杂度为O(n),其中n是输入数字的个数。这是最优解,因为至少需要遍历一次输入数据。
6.2 空间优化
可以使用更紧凑的存储方式:
- 用单个整数代替字符数组存储输入
- 用位运算代替计数数组
6.3 问题变种
- 组成最大数:类似思路,但按降序排列
- 允许前导零:直接排序即可
- 特定数字组合限制:增加额外约束条件
7. 常见错误与调试技巧
7.1 典型错误
- 未处理全0输入:会导致无输出或错误
- 忘记减少首位数字的计数:可能导致重复输出
- 数组越界:输入数字可能很长,需要足够大的缓冲区
7.2 调试建议
- 打印中间变量:检查计数数组是否正确
- 单步调试:观察首位选择过程
- 边界测试:特别关注全0或单个数字的情况
提示:在PTA系统中提交时,注意输入输出格式必须完全匹配题目要求,包括末尾的换行符。
8. 实际应用与延伸思考
这个问题虽然简单,但体现了贪心算法的核心思想:通过局部最优选择达到全局最优。在实际开发中,类似的思路可以应用于:
- 资源调度:将最短任务优先安排
- 数据压缩:高频字符用短编码
- 路径规划:选择当前最优路径
对于想进一步练习的同学,可以尝试以下扩展题目:
- 允许数字重新排列组合(不限于原始数字)
- 考虑负数和小数的情况
- 添加数字使用次数的限制条件
我在实际编码中发现,处理数字类问题时,边界条件的考虑往往比主要算法更重要。特别是在竞赛或考试中,一定要仔细测试各种特殊情况,这是区分优秀程序员的关键。