ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

PAT乙级1006《换个格式输出整数》:拆位、取模与循环控制一次讲透

PAT乙级1006《换个格式输出整数》:拆位、取模与循环控制一次讲透 PAT乙级真题刷到1006题《换个格式输出整数》的时候很多同学的第一反应是这题有什么好讲的15分小题一道不就是把数字拆开再拼回去吗。确实如果只追求AC这题五分钟就能拿下但如果你愿意多花一点时间把这道看似简单的小题拆开揉碎你会发现它刚好能把“取模运算”“整除运算”“循环控制”“输出格式”这几个入门阶段最容易含糊的点一次性讲清楚甚至能从中看到整个PAT乙级题型的出题套路。这篇文章就围绕这道PAT乙级1006题展开适合三类人刚开始刷PAT、对OJ环境还不熟悉的新手准备考研机试、但基础还不够扎实的备考者以及在学C语言对取模、整除和字符串拼接总是一知半解的初学者。我会从题目解读、三种解法、细节调试、高频考点四个角度把这15分背后的东西讲透。1. 先读懂题换个格式到底想让你做什么1.1 题目讲了个什么故事题面描述其实很简洁输入一个不超过3位的正整数 n如果它的百位是 a就输出 a 个字母 B十位是 b就输出 b 个字母 S个位是 c就输出从 1 到 c 的所有数字连成一个字符串。换句话说输出格式是B...B S...S 123...c给你两个例子234 会变成 BBSSS123423 会变成 SS123。第一次接触的同学容易把这道题想做“格式转换题”其实它本质上是“数字拆位 按规则输出”的模拟题。之所以叫“换个格式”是因为它把抽象的阿拉伯数字表达成了三种符号的组合百位用 B 的个数表达十位用 S 的个数表达个位则用从 1 开始递增的数字串表达。这种符号化输出在早年 PAT 题目里很常见考的就是你能不能把一个具体的数字拆成“百十个位”再根据每一位的值去做对应次数的循环输出。理解了题意你就知道这题卡的从来不是算法而是基础操作整除、取模、循环、格式化输出。这也是为什么我说它是一道被低估的“地基题”。1.2 拆解数字的底层逻辑取模与整除要解这道题第一关就是“把数字的百位、十位、个位分别拿到手”。这里要用到两个基础运算整除 / 和取模 %。生活类比想象你有一堆鸡蛋要装箱一箱装 10 个。整除就是问“能装满几箱”取模就是问“最后还剩几个装不满的零头”。对一个三位数 n 来说n / 100 就是在问“能装多少个一百的箱子”得到的正是百位n % 10 是问“去掉所有整十之后还剩几个”得到的正是个位而十位稍微绕一下先用 n / 10 把数字整体右移一位丢掉个位再对 10 取模就得到了原来的十位。用代码写出来就是int n 234; int bai n / 100; // 百位2 int shi n / 10 % 10; // 十位3 int ge n % 10; // 个位4这里特别提醒一句n / 10 % 10 的运算顺序是从左到右先算 n / 10再算 % 10。有不少同学第一次写时会写成 n % 10 / 10结果就变成了先取个位再除以 10永远得到 0这就是对运算优先级不熟悉导致的老实翻车现场。2. 三种解法逐步升级从硬判断到通用循环2.1 解法一if-else逐位判断最稳妥的“笨办法”最直观的思路就是先判断 n 到底有几位再分别拆百位、十位、个位。#include stdio.h int main() { int n; scanf(%d, n); int a 0, b 0, c 0; if (n 100) { a n / 100; b n / 10 % 10; c n % 10; } else if (n 10) { b n / 10; c n % 10; } else { c n; } for (int i 0; i a; i) printf(B); for (int i 0; i b; i) printf(S); for (int i 1; i c; i) printf(%d, i); printf(\n); return 0; }这段代码的逻辑很直白用 if-else 判断 n 是三位数、两位数还是一位数然后分别给 a、b、c 赋值。注意我给 a、b、c 都先初始化成 0再进行分支赋值这一步看似多余实际能避免“某一位没有被赋值却拿去参与循环”的危险。这种写法的优点是思路简单新手几乎不会写错缺点也很明显代码重复度高如果以后题目改成“不超过 9 位”或者“每位输出不同字符”你就得写更多 if-else很容易改着改着就漏掉一个分支。所以它适合作为理解题意的起点但不适合作为最终习惯。2.2 解法二while循环加数组位数不受限更通用一点的方案是不判断 n 有几位而是用 while 循环不停地取模和整除把每一位倒着存进数组。#include stdio.h int main() { int n; scanf(%d, n); int digits[3] {0}; // 数组全部初始化为0避免未定义值 int index 0; while (n 0) { digits[index] n % 10; n / 10; } // 循环结束后digits[0]是个位digits[1]是十位digits[2]是百位 for (int i 0; i digits[2]; i) printf(B); for (int i 0; i digits[1]; i) printf(S); for (int i 1; i digits[0]; i) printf(%d, i); printf(\n); return 0; }关键点在于循环里每次取 n % 10 得到当前最低位然后用 n / 10 把已经拆掉的位丢掉。这样每循环一次n 就少一位直到变成 0。因为最先取到的是个位所以 digits[0] 是个位、digits[1] 是十位、digits[2] 是百位和实际书写顺序正好相反输出时要倒过来看。这种解法的最大优势是通用即使题目把范围从 3 位改成任意位你的代码主体也不需要大改只要把数组开大一点就行。很多 PAT 甲级题里的“模拟数字位”其实都是这个套路。缺点是数组下标和数位的对应关系容易搞混我第一次写的时候就把个位和百位输出反了这时候在草稿纸上画一遍“数字到数组”的对应关系会非常有帮助。2.3 解法三用Python的字符串思维降维打击如果你刷 PAT 时用 Python代码可以短到让你怀疑人生n int(input()) a n // 100 b n // 10 % 10 c n % 10 print(B * a S * b .join(str(i) for i in range(1, c 1)))Python 的优势在于字符串可以直接用乘法做重复拼接B * a 就生成了 a 个 B.join 可以把 1 到 c 的数字序列拼成字符串。核心思路和 C 版本完全一样只是表达上更接近人类语言。如果改成字符串版本还能更“Pythonic”一点s input().strip() res if len(s) 3: res B * int(s[0]) s s[1:] if len(s) 2: res S * int(s[0]) s s[1:] res .join(str(i) for i in range(1, int(s) 1)) print(res)这个版本的思路是“不断地截掉已处理的高位”我觉得它更像是在模拟真实的考试场景你不需要把数字拆得那么数学而是直接把字符串按字符处理。两种写法各有各的好处但本质上都在重复同一件事——按位取值、按值循环。所以你看语言只是工具真正核心的永远是“拆位 循环”这套底层思路。把这个想明白了用 C、用 Python、用 Java 写都只是翻译工作。2.4 三种解法怎么选从“能过”到“好用”这里总结一下三种方案的取舍解法核心思路代码量易错点扩展性if-else 分支先判断位数再拆位较少分支写漏、边界判断错差位数一多就崩while数组循环取模存数组中等数组下标顺序搞反好位数变化只需改数组长度Python 字符串字符串切片乘法最少输入含空格/换行时需 strip好且贴近真实开发选哪种取决于你处于哪个阶段第一次接触就多用 if-else 理解过程练到一定量后用 while数组形成肌肉记忆顺便为字符串、数组相关题型打基础如果只想快速 ACPython 字符串版当然最省事。但我建议至少把前两种 C 语言写法都亲手敲一遍因为 PAT 考试主推的仍然是 C/C你最后在考场上依赖的应该是熟到不用思考的那一套。3. 实操过程中最容易踩的四个细节坑3.1 边界用例把1到999都测一遍这题的分值是 15 分属于水题但“水题”最容易在边界上翻车。我建议拿到任何模拟题先把边界用例老老实实过一遍。对于这道题核心边界包括输入预期输出说明11最小一位数数字串从1开始9123456789个位最大数字串最长10S十位是1个位是0数字部分为空99SS123456789十位最大个位最大100B只有百位S和数字都为空234BBSSS1234题目样例999BBBBBBBBBSSSSSSSSS123456789最大输入特别注意 10 和 100 这两个输入当个位是 0 时规则说“输出 1 到 c 的数字”1 到 0 是没有内容的所以数字串应当为空。很多同学第一次写会习惯性地想让个位输出一个 0一测样例就错反过来说如果你用 for (int i 1; i c; i) 这种写法c0 时循环体一次都不执行天然就是正确的。我自己的习惯是代码写成后先把 {1, 2, 9, 10, 90, 99, 100, 234, 999} 这些值挨个跑一遍。这个过程能帮你把逻辑漏洞自动暴露出来比对着题目发呆有效得多。3.2 输出换行OJ的格式扣分点PAT 这类在线评测系统对输出格式的要求非常死板多一个空格、少一个换行、多一个空行都可能直接判 Wrong Answer。本题的“标准答案”通常在末尾包含一个换行符如果你 printf 完不补 printf(\n)在本地终端里可能看不出问题因为下一个命令提示符会顶上去但提交到 OJ 上就会因为输出不匹配而被判错。正确做法是养成习惯所有题目最后都要输出完整的一行即输出末尾一定带上 \n。如果你不确定该不该有换行可以看题目的“输出格式”说明一般都会写“每行输出”或“输出占一行”。本题就是“在一行中输出”所以结尾加换行准没错。不要觉得这是小事PAT 乙级很多同学第一次提交拿到的不是 AC下来一排查一半以上都是换行、空格这种格式问题。把格式当成题面的一部分去读能省下大量调试时间。3.3 数组初始化与scanf取地址符代码写出来但本地运行结果不对常见的两个低级错误第一个数组没初始化。如果你写 int digits[3]; 而不是 int digits[3] {0};那么对于输入 9 这种只有一位的数循环只给 digits[0] 赋值digits[1] 和 digits[2] 会保留栈里的随机垃圾值随后你拿垃圾值去控制循环次数输出就会变成一团乱码。C 语言不会自动把局部数组清零这跟 Java、Python 不一样必须自己初始化。第二个scanf 漏了取地址符 。新手最经典的错误是 scanf(%d, n); 把 n 的值当地址传进去程序直接崩溃或者行为未定义。记住 scanf 系列函数的参数必须是指针变量传入前要加 数组名本身是地址所以不用加。每次提交前扫一眼这两处能避免绝大多数入门期翻车。3.4 把核心逻辑封装成函数刷题界的好习惯题目虽然简单但我建议你从这道题开始就养成一个习惯把核心逻辑抽成独立的函数而不是全堆在 main 里。比如#include stdio.h void printFormatted(int n) { int a n / 100; int b n / 10 % 10; int c n % 10; for (int i 0; i a; i) putchar(B); for (int i 0; i b; i) putchar(S); for (int i 1; i c; i) putchar(0 i); putchar(\n); } int main() { int n; scanf(%d, n); printFormatted(n); return 0; }好处有两个。一是 main 变得非常短出错时一眼就能看到问题在哪二是你可以非常方便地在本地写一个循环把多组测试用例一次性喂给函数不需要反复手动输入。后面刷到复杂题时这种“小函数 自测驱动”的写法会帮你节省大量时间。4. 从1006看PAT乙级的高频考点与刷题策略4.1 数字拆解三步法取模、整除、循环做过几道 PAT 乙级题就会发现数字拆解几乎是必考能力。它有一个固定套路第一步用 % 10 取出当前最低位第二步用 / 10 把已取出的位从原数上抹掉第三步把前面两步放进 while 循环直到原数变成 0。这个过程可以逐个取出任意十进制整数的每一位不限于三位。同样这套“三步法”往后再刷题你会见到无数变体逆序输出一个数就是每次把取出的数字拼接成新数求各位数字之和就是每次把取出的数字累加统计某数字出现次数就是每次用取出的数字作为数组下标给计数器加一。1006 刚好把所有基础操作浓缩在一道题里你把它吃透后面十来道题都会顺畅很多。如果你也是跟着翁恺老师的 C 语言课入门的配合 PTA 平台把这套基础题单做扎实会对后续课程理解帮助很大。4.2 和1006同类的几道真题建议连起来刷题号题名和1006的关联1002写出这个数都要拆各位数字1002拆完求和再用拼音输出1001害死人不偿命的(3n1)猜想循环分支与取模的混合训练1021个位数统计拆位后按数字0-9计数数组哈希思想1036跟奥巴马一起编程用循环控制字符输出的模式和这里B/S输出同构1076Wifi密码字符串处理按条件输出考察同一类基础操作我推荐把这些题放在一个刷题周期里连续做。你会发现各题表面上完全不一样但骨架惊人地相似读入、拆解/遍历、按规则输出。能把这种“骨架感”练出来说明你已经从“背代码”过渡到“写代码”了。4.3 给新手的刷题节奏建议如果你是零基础或者刚学 C 语言不久我的建议是不要按题号顺序从 1001 一路啃下去而是按知识点分组刷题。第一组做 1001、1006、1036重点练循环和输出格式第二组做 1002、1021、1076重点练数字拆解和字符处理第三组再去做 1011、1016、1026、1031、1041 这些综合题体会条件判断、循环、数组的组合使用。每一组结束后停下来做一次总结这组题考了什么语法、什么思路、我犯了什么错。如果时间和精力允许再用另一种语言把同一道题重新写一遍比如 C 写完后用 Python 写这种“横切对比”非常能加深你对语言特性和算法本质的理解。备考机试的同学尤其要注意做题不在多而在精把 60 道基础题反复吃透比囫囵吞枣做 150 道更有价值。5. 常见问题与排查技巧速查5.1 编译错误到底卡在哪一步本地运行好好的提交后却编译失败最气人。本题常见的编译错误有两种。第一for 循环里直接声明变量例如 for (int i 0; i a; i)如果 OJ 使用的编译器没有开启 C99 或更高标准会报 “for loop initial declarations are only allowed in C99 mode”。解决方法是把循环变量提到函数开头统一声明例如 int i; for (i 0; ...)或者确认 OJ 的编译选项支持 C99。第二输出函数名写错printf 写成 print 或 println这在从其他语言转过来的人里非常常见。C 语言的格式化输出只有 printf没有 println。编译错误看提示信息其实都能定位到行号关键是要养成“先看报错行号再往上翻几行看上下文”的排查习惯。5.2 答案错误边界条件与逻辑漏洞答案错误里最典型的是把 10、100 这种带零的数输出错。如果你写的是“先判断是几位数再分别处理”很可能会漏掉“十位为0时个位是否能正确输出”这个分支。说实话备考阶段答案错误是件好事——OJ 会明明白白告诉你错了这时候就会逼着你回头想哪个样例跑挂了是输出多了、少了还是格式不对我的排查方法是三步先跑题目给的样例确认输出和样例完全一致包括换行和空格再跑边界值表也就是第 3.1 节里列的那一组最后如果还是错就写一个小循环把 1 到 999 全部过一遍人工比对前几个错误结果。这一步做完逻辑漏洞基本就无所遁形了。5.3 一个实用的自测小技巧全量跑一遍最后分享一个我到现在写算法题还在用的土办法写一个临时测试函数把 1 到 999 的所有输入值全部跑一遍输出到一个文件里再和别人写的参考程序生成一份标准答案用工具 diff 一下所有不同点立刻暴露。对于这道题全量测试其实不需要写到文件直接在本地跑一个循环调用 printFormatted 就行int main() { for (int n 1; n 999; n) { printf(%04d - , n); printFormatted(n); } return 0; }看到输出结果基本原则一眼就能发现异常比如某个数位空着、某段数字缺失、顺序倒了。这种“暴力全量自测”虽然土但在入门阶段特别有效它比你自己凭空想十个测试用例靠谱得多。以后刷到更复杂的模拟题这个习惯也能帮你大幅度减少“提交-失败-再提交”的循环次数。我个人是在一次模拟赛里偶然发现这个方法的当时一道字符串模拟题反复错靠肉眼怎么看都正常最后把整段输入全量跑了一遍才定位到是某个特殊字符没处理。从那以后凡是能枚举完的输入范围我一定会先全量自测。这道 1006 虽然简单但它足够适合你把这套自测流程练熟把好习惯沉淀下来后面遇到更大的坑时你会感谢现在的自己。
返回列表