ARTICLE DETAIL

资讯详情

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

PAT乙级1002题解:写出这个数,避开新手常见坑

PAT乙级1002题解:写出这个数,避开新手常见坑 PAT乙级1002这道题在OJ圈子里几乎算得上“梦开始的地方”。我记得自己刚接触PAT那会儿第一次正经跑通的题就是它——题目短需求直白但就是这种看似简单的题反而最能暴露基本功的窟窿。想写这篇东西其实有个很直接的起因我前前后后帮不少刚入坑算法题的朋友看过代码发现很多人在1002上不是不会做而是栽在一些特别低级又特别隐蔽的细节上。所以这篇就围绕PAT乙级1002展开既把标准解法讲透也把容易丢分的地方掰开揉碎说清楚。不管你是刚开始刷PAT的新手还是想帮别人讲题的老手这内容应该都能让你有点收获。1. 题目本身在说什么一次拆完别留疑问1.1 原题要求只有三句话PAT乙级1002题目名是“写出这个数”。原题描述不复杂读入一个正整数 n计算它的各位数字之和然后用汉语拼音写出这个和的每一位数字。这里有几个关键信息要圈出来输入是一个正整数不是负整数也不是小数。要求的不是输出这个数本身而是先做一个“数位求和”再对和做“拼音转换”。输入上限是 n 10^100也就是最多100位十进制数。输出的每个拼音之间要有一个空格行末不能有多余空格。举个例子输入1234567890987654321123456789先算数位和。这串数拆开加一遍结果是135那么输出就是yi san wu。注意是“三”的拼音san不是“山”是“五”的拼音wu不是“无”。这种细节就是PAT判题系统最喜欢埋的点。1.2 它真正在考察的四个基本功别看题目简单它其实同时考察了四个东西。第一输入输出格式的严谨性。PAT的评判对格式要求非常苛刻最后多一个空格、少一个换行、拼音大小写写错都可能直接判错。很多人算法思路没问题却在格式上反复交学费。第二大数处理意识。看到“正整数 n”第一反应是int这确实是新手默认思路。但题目明确写了n 10^100这个量级根本不是C语言里任何整型能装下的long long最大也就约9.2×10^18差远了。所以必须换赛道用字符串来接收输入。第三字符与数字的转换。用字符串读入之后每一个字符都是0到9这样的ASCII字符要参与加法运算就得先转换成对应的整数值。最常见也最标准的方式就是s[i] - 0。第四数组映射和输出拼接。把0到9的拼音放进一个字符串数组然后用和值的每一位数字去索引对应的拼音这是一种非常基础的“查表”思路。后面做进制转换、做哈希映射的时候这个思路会反复出现。1.3 为什么说是“开胃菜”却总有新手翻车我在不同的学习群里看过不下二十个版本的1002代码。大部分人第一次都能写出来一个“看起来能跑”的版本但真正提交上去就发现过不了。翻车集中在几种情况用int n; scanf(%d, n);接收输入一提交才发现运行错误或者答案错误。把每一位数字直接相加时忘记减去0得到的和莫名其妙地比预期大很多。拼音表只写了9个元素访问pinyin[9]的时候越界或者输出为空。最后多打了一个空格被系统提示“格式错误”。说实话这些错误单独拎出来都不值一提但它们恰恰说明一个问题刷题的第一个门槛不是算法是“准确理解题目到底要什么并且严格按照要求输出”。搞定这一点1002就是一道热身题搞不定后面每道题都会在同一个坑里摔一次。2. 解题思路演进从直觉方案到正确方案2.1 第一反应为什么必然翻车看到“算各位数字之和”绝大多数人的第一反应是int n; scanf(%d, n); int sum 0; while (n 0) { sum n % 10; n / 10; }这个思路本身没错错在第一步。题目输入上限是10^100也就是最多100位数字。C语言里int通常只有32位最大才21亿多long long虽然有64位最大也就约922京9.22×10^18。100位十进制数远远超出这个范围。可以直观感受一下10^100这个数是1后面跟100个0哪怕你用long long也只能准确表示到10^18左右高位数据全部丢失。一旦数据溢出计算出来的数位和就错得离谱。所以这道题的第一课就是看到超大数别往数值类型里装老老实实用字符串。这不是什么高深的技巧就是一个工程常识——数据的存储方式要跟数据本身的形态匹配。2.2 字符串读入才是正解正确的做法是把输入当成一串字符处理。C语言里用scanf(%s, str)读入或者用C的cin strPython直接input()Java用Scanner.next()。它们的共同点是不关心输入数字有多大只按字符逐个读入。读进来之后数位和的计算就变成了遍历字符串int sum 0; int len strlen(str); for (int i 0; i len; i) { sum str[i] - 0; }这里有两个容易犯的毛病。第一个是把str[i] - 0写成str[i]。如果输入的第一个字符是1它的ASCII码是49你直接加进去求和结果就会偏大48。第二个毛病是用strlen的时候忘了带头文件string.h编译直接报隐式声明警告虽然有些环境下还能跑但属于定时炸弹。至于数位和的存储类型int完全够用。因为输入最多100位每一位最大是9和的最大值不会超过900一个int装得绰绰有余。这也是题目设计里比较体贴的地方输入很大但中间结果很小。2.3 和的每一位怎么拆各有各的写法数位和算出来之后面临第二个问题把和的每一位按从左到右的顺序输出拼音。和的最大值是900最多三位所以方法很多。第一种是直接用sprintf把整数转成字符串char res[100]; sprintf(res, %d, sum);然后遍历res把每一位字符转换成数字再去查拼音表。这个方案最直接而且因为sprintf天然把整数转成高位在左的字符串输出顺序不用额外处理。第二种是“除10倒着存”。循环里用sum % 10依次取出个位、十位、百位存进数组最后逆序遍历数组输出。这个方案不依赖sprintf但要多写几行而且容易在逆序的时候把下标搞错。第三种是递归输出代码很优雅void print_sum(int sum) { if (sum / 10) { print_sum(sum / 10); } printf(%s, pinyin[sum % 10]); }递归进去再出来天然保证了高位先输出。不过新手对递归还不熟的时候不建议为了炫技用这种方式等理解透彻了再用不迟。我个人更推荐sprintf方案。理由很简单可读性好代码短不容易在边界条件上犯错。刷题的首要目标是正确AC不是写得最玄乎。3. C语言标准实现与那些必须知道的输出细节3.1 一份可以直接AC的参考代码下面这份是我很常用的C语言写法干净、直白适合作为模板#include stdio.h #include string.h int main() { char str[105]; scanf(%s, str); int sum 0; int len strlen(str); for (int i 0; i len; i) { sum str[i] - 0; } char *pinyin[] {ling, yi, er, san, si, wu, liu, qi, ba, jiu}; char res[100]; sprintf(res, %d, sum); int res_len strlen(res); for (int i 0; i res_len; i) { if (i ! 0) { printf( ); } printf(%s, pinyin[res[i] - 0]); } printf(\n); return 0; }这里我开了char str[105]而不是char str[101]纯粹是习惯性多留一点余量。题目说输入不超过100位加上字符串结尾的\0最低要求是101个字符开105不会错。char *pinyin[]这种写法表示一个字符串数组每个元素指向一个字符串常量。索引从0到9刚好对应数字0到9的拼音。注意不要写成char pinyin[]那是二维字符数组的另一种声明容易跟一维数组混淆。3.2 输出格式的魔鬼细节PAT对输出格式的检查非常细我见过太多人死在空格上。看输出部分for (int i 0; i res_len; i) { if (i ! 0) { printf( ); } printf(%s, pinyin[res[i] - 0]); }这个写法的逻辑是第一个拼音前面不输出空格从第二个开始每个拼音前面补一个空格。这样出来的结果就是yi san wu而不是yi san wu末尾多空格也不是yisanwu。反面写法是for (int i 0; i res_len; i) { printf(%s , pinyin[res[i] - 0]); }这个会在最后一个拼音后面也带一个空格。如果PAT判题系统对行末空格做精确比对这就是一个“格式错误”或“答案错误”。另一个细节是换行。大部分PAT题目要求输出最后带一个换行符所以我在最后加了printf(\n)。有的同学觉得不带换行也能过确实有些题目的判题逻辑不卡这个但不是所有题都这样。与其赌运气不如每条输出都规规矩矩带换行。3.3 关于“和值为0”这种边界情况PAT的输入明确是正整数所以理论上数位和最小也是1不会出现0这种情况。比如输入1000数位和是1输出yi。但如果题目改成了“自然数”允许输入0那数位和就是0应该输出ling。在我上面这份参考代码里sprintf(res, %d, 0)得到的是字符串0循环遍历时res[0] - 0恰好是0查表得ling。也就是说这段代码天然兼容了sum 0的情况不用额外加if判断。这个设计纯属顺手但确实省心。当然如果要追求绝对的严谨可以在计算完后加一句if (sum 0) { printf(ling\n); return 0; }两种写法都行看个人习惯。我平时倾向于让代码自然包含边界避免为不存在的输入增加分支。3.4 实测感受与编译注意事项我用这份代码在PAT系统提交过编译选项是标准自带直接AC运行时间几乎为0毫秒内存占用几百KB。就这道题的规模来说性能和内存根本不构成任何压力别担心。需要注意几个编译级别的问题一定要#include string.h否则strlen没有声明。sprintf需要stdio.h这个一般不会漏。如果用的是较新编译器scanf可能建议改成scanf_s但PAT的OJ环境是Linux下的GCC直接scanf没问题。还有不要在代码里用gets(str)来读输入。gets不检查缓冲区边界早就被移出标准了。而且读入的内容可能带换行处理起来反而麻烦。scanf(%s, str)会跳过空白字符输入又是单个数字串用它最干净。4. 换个语言再看一遍Python、Java和C的写法差异4.1 Python三行代码但有一个反直觉的坑如果允许用Python刷PAT乙级1002的解法可以写得非常短s input() total str(sum(int(c) for c in s)) pinyin [ling, yi, er, san, si, wu, liu, qi, ba, jiu] print( .join(pinyin[int(ch)] for ch in total))核心逻辑就三行。int(c) for c in s把输入的每个字符转成整数sum()求和再str()转回字符串最后逐位查表并用空格拼接。但这里面有个反直觉的地方如果直接用input()读假设输入是0虽然PAT不给这种数据sum(int(c) for c in s)是0str(0)是0输出ling没问题。真正要注意的是Python的int不限制大小你其实可以不转字符串直接求但那样做没有任何好处反而让代码绕了一道。另外Python里input()一次读一行如果OJ的测试数据文件末尾有额外换行input()会自动去掉不用额外strip()。但如果你用了sys.stdin.read()读整个文件就得小心末尾换行被当成输入的一部分。4.2 Java用String接收再用charArray遍历我第一次用Java写这题时第一反应是BigInteger后来想想纯属多此一举。因为既然只是数位求和根本不需要把输入变成数值直接按字符串处理就行。import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner sc new Scanner(System.in); String n sc.next(); int sum 0; for (int i 0; i n.length(); i) { sum n.charAt(i) - 0; } String[] pinyin {ling, yi, er, san, si, wu, liu, qi, ba, jiu}; String total String.valueOf(sum); StringBuilder sb new StringBuilder(); for (int i 0; i total.length(); i) { if (i 0) { sb.append( ); } sb.append(pinyin[total.charAt(i) - 0]); } System.out.println(sb.toString()); } }这里的坑在于String.charAt(i)返回的是char直接参与加法时要记得- 0。另外用StringBuilder拼接字符串比连接更高效虽然在1002这种小规模问题上差距可以忽略但养成好习惯后面处理长字符串时会受益。Java类名必须是Main不然OJ找不到入口。这个忘了我第一次也犯了很冤枉。4.3 Cstring和数组的结合最顺手C的写法其实和C很像但std::string让长度获取和字符遍历更自然#include iostream #include string using namespace std; int main() { string s; cin s; int sum 0; for (char c : s) { sum c - 0; } string pinyin[] {ling, yi, er, san, si, wu, liu, qi, ba, jiu}; string total to_string(sum); for (int i 0; i total.size(); i) { if (i ! 0) { cout ; } cout pinyin[total[i] - 0]; } cout endl; return 0; }C11之后的范围for让字符遍历变得很舒服。这里唯一要注意的是total[i]返回的是char和Java一样要减去0再当下标用。我自己的体会是这道题用C语言写最能理解底层细节用Python写最省事用C写最均衡。如果你还在学习阶段建议至少把C语言的版本完整手敲一遍别一上来就用Python带过否则会错过字符串和字符转换这个关键基本功。5. 从1002说开去PAT乙级刷题路线与常见教训5.1 通过这道题之后我建议的刷题顺序1002在PAT乙级里的位置相当于“体检项目”它帮你在入门阶段确认自己有没有方向性的大问题。通过之后可以按照下面这个顺序继续题号题目要点为什么推荐放在前面1001(3n1)猜想循环和条件判断的入门组合1008数组元素循环右移理解数组下标运算和取模1011AB 和 C基础输入输出注意数据范围1016部分AB字符处理和数位拼接1037在霍格沃茨找零钱进制换算思想趣味性强不要一上来就挑战高分难题。乙级题目的特点是“考基本功不考偏题”把上面这些题刷完你基本上就把循环、数组、字符串、格式化输出这些核心能力盘活了。之后再往上走去看甲级的时候才会轻松一些。5.2 PAT乙级的“十年老坑”清单刷题数量上来之后很多错误其实是重复的。我总结了一张高频踩坑清单错误类型典型表现正确做法数据范围判断错误用int读大数溢出后答案错见“超大数”就考虑字符串字符忘记转数字sum s[i]结果偏大用s[i] - 0拼音表越界pinyin[sum]sum超过9先转成字符串逐位处理输出末尾带空格每个拼音后都加空格第一个前面不加之后补空格域名拼写错误“si”写成“shi”“ba”写成“pa”多读题示例提交前自查类名/功能名问题Java类名不是Main按OJ要求命名这些坑没有一个是“算法不会”全是“审题不细”或“基础不稳”。但它们造成的扣分比想象中严重因为PAT判题是零容忍的一个字符不符就是整个测试点不得分。5.3 比代码更重要的能力读题和自测我知道很多人刷题的习惯是看到题目就上手写代码恨不得三分钟提交。但1002这种题最能教育我们读题两分钟胜过改码两小时。拿到题之后先做三件事圈出输入范围。看到10^100第一时间想到字符串。圈出输出格式。看到“拼音之间空格分隔”第一时间想到循环拼接规则。手算一遍示例。把输入的示例自己算一遍确认和输出对得上再开始写代码。这三件事看着不起眼却能避开至少80%的基础错误。我后来养成的习惯是写完代码之后先把题目给出的示例跑一遍再自己构造两组边界数据——一组是最大长度输入一组是可能导致和值最大的输入比如100个9。这两组数据如果都能正确输出这道题基本就稳了。6. 个人操作中的一点体会最后聊点我自己的经验吧。实话实说1002不是一道能让人产生“我很强”错觉的题它更像是算法路上的一把尺子量一量你对输入输出、数据类型、字符处理这些基本功掌握到什么程度。我见过有的同学在这道题上反复提交七八次每次都被不同的小细节挡住然后很沮丧地来问我是不是不适合写代码。其实完全不是。PAT这种判题系统就是很“死板”它不会因为你思路对就手下留情任何格式问题都是错误。但这恰恰是好事——早早在入门题上被教做人总比到了职场交付项目时被线上Bug折磨要强。如果你现在正在1002上挣扎我的建议是不要急着看别人的满分代码先把你自己写的版本跑一遍一个测试点一个测试点地看错误输出找到问题所在。改完再提交再发现问题再改。这个过程本身比最终那个AC的绿色结果值钱得多。
返回列表