资讯动态

PAT乙级1015德才论:多关键字排序与Python实现全解析

发布时间:2026/10/14 8:55:09 来源:尧图企业网站定制
如果你正在刷在线编程题库遇到“德才论”这道题大概率会被它的分类和排序绕晕。题目标号是1015难点不在于算法本身而在于怎么把题目的规则精确翻译成代码逻辑以及处理一堆隐藏的测试点。这一篇我把完整思路、Python实现和踩过的坑一起整理出来适合正在刷题准备机试的初学者也适合想系统梳理排序分类思路的朋友。1. 题目到底在考什么排序还是分类1.1 两条线最低录取线与优先录取线题目会给N个考生每个考生有考号、德分、才分三个属性。录取规则先看两条分数线最低录取线L和优先录取线H。德分和才分都达到L的考生才“有资格”任何一门低于L的直接排除不问理由。注意这里用的是“都达到”也就是说只要德分或者才分有一门低于L这个人就出局了不管另一门多高。很多人一上来就想着“模拟录取流程”其实不用这么复杂。题目的本质就是分类加排序先把合格的人挑出来然后按德才表现划分成四类再按统一规则排序输出。注意L和H都是整数边界判断条件一定要写成 L而不是 L。测试点专门会卡恰好相等的情况差一个等号就是错一排。1.2 四类考生的划分规则德才论把合格考生分成四类优先级从高到低规则如下类别条件含义第一类德分 H 且 才分 H德才兼备全优第二类德分 H 且 才分 H德高分低才分不够优秀第三类德分 H 且 才分 H 且 德分 才分德才都低于H但有德优先第四类达到L但不符合以上三类过线但有瑕疵排在最后这里的第三类最容易被写错。注意它是“德分 H”且“才分 H”德分不低于才分。如果一个人的德分和才分都低于H但才分比德分高那只能归为第四类。我当时第一版代码就把第三类写成了 else if de cai没有限定德分才分都低于H直接把第四类的一部分划到了第三类里测试点挂得很惨。1.3 输入和输出的格式陷阱输入的第一行是三个整数考生总数N、最低线L、优先线H。接下来有N行每行是考号、德分、才分。注意考号在题目里通常是一个定长数字编号读进来后可以当作字符串处理输出时原样输出就行。输出格式要求第一行输出实际录取人数接下来按排序结果逐行输出考号、德分、才分。这里有一个容易忽略的点如果录取人数为0第一行输出0后面不要有任何多余空行。用字符串拼接时很容易多出一个换行这一步坑了不少人。2. 排序规则的降维多关键字排序怎么设计2.1 优先级拆解四级排序键题目给出的排序规则是类别优先级高的先输出同类别下按总分从高到低总分相同时按德分从高到低德分还相同按考号从小到大。翻译过来就是排序键的四级结构类别编号0到3越小越靠前总分降序德分降序考号升序注意这里“德分还相同按考号从小到大”考号是数字编号但题目里不会出现负数用字符串正序排序即可前提是所有编号长度一致。如果长度不一致字符串排序会出现“9”排在“10”后面的反直觉结果。2.2 元组与负号Python排序的核心写法Python的列表排序可以传一个元组作为key元组比较时逐元素比较。要点是有三个类别编号默认升序0、1、2、3正好对应要求。总分、德分需要降序而元组排序默认升序所以用负号取相反数。考号用原值升序。例如第一类的排序group[0].sort(keylambda x: (-(x[1] x[2]), -x[1], x[0]))x[1]是德分x[2]是才分。总分取负德分取负考号原值放第三个。运行效果就是总分降序总分一样时德分降序德分一样时考号升序。这个写法比自定义比较函数快得多代码也简洁。2.3 分数相等最容易翻车的细节如果两个人的总分和德分都相同就比较考号小的排前面。考号是数字编号时字符串升序在小端位数一致的情况下等价于数字升序所以用字符串直接比较没问题。保险做法是读入时保留字符串形式排序时原样使用。有人写排序键时手滑写成 -x[0]导致考号倒序输出这种错误不容易在随手的样例里发现因为普通测试数据不会专门构造总分、德分都相同的组合。所以我建议把排序键写完后默念一遍“总分负、德分负、考号正”确认无误再提交。3. Python 实现从零到满分的完整链路3.1 读取输入三种方式怎么选N的最大值在题目里可以达到十万级别虽然不算特别大但如果在Python里用 input() 逐行读取几十万次耗时很难看。这里我推荐用 sys.stdin.buffer.read() 一次性读取全部输入再按空白字符拆分速度非常稳。import sys def main(): data sys.stdin.buffer.read().split()读进来后 data 是字节串列表int() 能直接解析字节串但考号需要解码成字符串否则后续拼接输出时会带上 b 前缀。所以我在循环里用 decode() 处理考号。实际对比看三种方式的取舍如下读取方式适合场景速度input()N很小最多几千较慢sys.stdin.buffer.readline()N几万到十几万中等sys.stdin.buffer.read()N十万以上最快这道题N通常在十万以内但为保险起见直接采用 read 方案代码改动不大收益却很明显。3.2 筛选与分类写在一遍循环里读取每个考生三元组时先判断是否达到最低线L没达到直接跳过达到了再判断属于第几类把三元组加入对应类别列表。这样一个循环同时完成筛选和分类后面排序之前已经按类别分好省去了先全部塞一个列表再二次分类的麻烦。n, L, H int(data[0]), int(data[1]), int(data[2]) groups [[], [], [], []] idx 3 for _ in range(n): num data[idx].decode() de int(data[idx 1]) cai int(data[idx 2]) idx 3 if de L or cai L: continue if de H and cai H: groups[0].append((num, de, cai)) elif de H and cai H: groups[1].append((num, de, cai)) elif de H and cai H and de cai: groups[2].append((num, de, cai)) else: groups[3].append((num, de, cai))注意第二类我用的是 de H and cai H写完整条件语义更清晰。理论上走到这一步且 de H 时cai 不可能也 H因为那样已经进了第一类但显式写出来能减少心智负担。第三类同理走到这里的人已经排除前两类de 不可能 H但把 de H and cai H 都写出来读代码的人一眼能看懂是题目原意。3.3 输出设计避免多余换行输出时先统计总人数然后把各类别排序后的结果拼接成字符串列表最后用一次 sys.stdout.write 输出避免 print 多次调用带来的性能损耗。lines [str(sum(len(g) for g in groups))] for g in groups: g.sort(keylambda x: (-(x[1] x[2]), -x[1], x[0])) for num, de, cai in g: lines.append(f{num} {de} {cai}) sys.stdout.write(\n.join(lines))如果录取人数为0lines 里只有 0write 不会在末尾多加换行正好满足要求。之前我试过在末尾再 append 一个空字符串结果输出多了一个换行被判定格式错误。3.4 完整可运行的参考代码下面是一份可以直接跑通的完整代码注释写在关键位置import sys def main(): data sys.stdin.buffer.read().split() if not data: return n, L, H int(data[0]), int(data[1]), int(data[2]) groups [[], [], [], []] idx 3 for _ in range(n): num data[idx].decode() de int(data[idx 1]) cai int(data[idx 2]) idx 3 if de L or cai L: continue if de H and cai H: groups[0].append((num, de, cai)) elif de H and cai H: groups[1].append((num, de, cai)) elif de H and cai H and de cai: groups[2].append((num, de, cai)) else: groups[3].append((num, de, cai)) lines [str(sum(len(g) for g in groups))] for g in groups: g.sort(keylambda x: (-(x[1] x[2]), -x[1], x[0])) for num, de, cai in g: lines.append(f{num} {de} {cai}) sys.stdout.write(\n.join(lines)) if __name__ __main__: main()这份代码在时间复杂度和空间复杂度上都没有明显瓶颈读入、分类、排序、输出四个环节都用了比较稳妥的写法刷题平台实测下来表现稳定。4. 测试点问题实录与排查技巧4.1 全员淘汰输出零人时别留空行最典型的边界情况是所有考生都有一门或两门低于L录取人数为0。此时应该输出一个单独的 0后续什么也不输出。我第一次提交时在 lines 列表末尾多加了一个空字符串结果输出多了一个换行被判格式错误。排查思路很简单本地构造几组全是低分的数据跑一遍用十六进制工具看输出末尾有没有多余的 \n。我后来改成只在列表里放 0 并用 join 拼接问题彻底解决。4.2 同分不同名次的隐藏用例题目会专门构造一组总分、德分都一样考号乱序的数据用来检测考号排序有没有写对。常见的错误就是排序键里不接考号或者手滑给考号加了负号。这里记住一句话考号升序不加负号。另外还要注意如果考号是整数而不是字符串排序键同样写 x[0] 正序即可。但如果你把考号解码成了字符串那就要确保所有编号等长否则“100”和“99”会排成“100”在前、“99”在后不符合数字大小顺序。4.3 输入规模大导致超时当N达到几十万时如果还用 input() 逐行读取Python很容易超时。我的排查习惯是先把输入输出部分替换成 sys.stdin.buffer 方案再提交看结果。大部分时候超时问题都出在 IO而不是排序本身。排序那一步的时间复杂度是 O(N log N)在十万规模下完全没问题。真正拖后腿的是万次级别的 print 调用因为每次 print 都要做一次系统调用累积起来耗时很客观。4.4 第三类条件边界分支顺序的坑题目描述里第三类要求“德才分均不及 H 但德分 才分”。这个“均不及H”是很多人漏掉的条件。如果你写出这样的判断链第一类de H and cai H第二类de H隐含 cai H第三类de cai那么当一个考生 de H、cai H、de cai 时第三类条件 de cai 不成立会掉进第四类逻辑正确。但如果有人把第二类写成 de H or cai H或者把第三类放在第二类之前那结果就完全乱了。我的建议是写完整条件不要依赖分支之间的隐含关系。代码长了点但每个条件都是自解释的调试时不容易把自己绕进去。4.5 筛选条件里的“达到”边界L 和 H 是整数分数线“达到L”意味着 de L 且 cai L“达到H”意味着 de H 且 cai H。很多人在第一版会写成 de H结果恰好等于 H 的考生被错误排除导致通过率掉一个测试点。我自己就栽过一次当时为了省字符把 de H and cai H 写成了 de H-1逻辑上等价但少考虑负数分数的情况。如果测试点里出现负数这个写法就会出问题所以最好老老实实用 直观表达。5. 避坑技巧与高效写题习惯5.1 分类逻辑先画分支图再写代码面对这种多条件分类我习惯先在白纸上写一个分支树把所有可能的情形列出来。德分和才分相对于H的位置一共有四种组合再加上L的限制总共没多少分支。画完分支树再写代码顺序永远清晰不会漏条件。5.2 善用列表分组代替额外类别字段因为题目要求先按类别输出再按分数排序所以我在读入时就按类别分到四个列表每个列表内部再排序。这样不需要在元组里额外保存类别编号也避免了排序时类别编号影响分数排序的麻烦。如果把所有合格考生放进一个列表再排序就需要在排序键里加上类别字段例如all_list.sort(keylambda x: (x[3], -(x[1] x[2]), -x[1], x[0]))其中 x[3] 是类别编号。这样做也能正确但每个元组多存一个字段逻辑上不如分组清晰。5.3 善用 sys.stdin.buffer 与格式化拼接刷题时 IO 性能往往决定这道题能不能通过。我的固定习惯是只要输入数据量超过一万就无脑用 sys.stdin.buffer.read().split()输出用列表收集后一次性 write。这个习惯帮我避开了很多次超时。格式化输出时用 f{num} {de} {cai} 比用 %s %s %s % (...) 可读性更好性能差别也不大。测试数据量大时f-string 的解析速度已经足够快不用刻意优化。5.4 构造自己的随机测试样例与其盯着测试点一个个猜不如写一个简单的数据生成器随机生成几百组数据再用暴力排序方法对照验证自己的代码结果。具体做法是写一个简单的参考实现比如用 sorted 函数配合完整四元组排序。随机生成 N、L、H 以及若干考生数据。对比两个程序的输出是否完全一致。发现不一致再打印具体输入定位问题。这个方法在排查排序和边界条件时非常高效比反复提交测试代码省时间得多。6. 个人经验与后续扩展说一点刷题之外的体会。德才论这道题本质上是在考察“把自然语言规则转成程序逻辑”的能力。题目背景借用了古代的选人标准但落到计算机上就是分类、筛选、排序三件事。能把这些步骤拆干净代码自然清晰测试点也自然好处理。我前后提交过很多次第一次挂在第三类边界第二次挂在全员淘汰时多输出了一个空行第三次才彻底通过。后面每次做类似的排序分类题我都会先把类别划分写成明确的条件分支再用元组排序一把梭基本不会再在这些细节上出错。如果你也想在同类题上少走弯路可以把这道题的思路迁移到其他“多关键字排序”问题上比如学生综合测评排名、比赛晋级排序等。套路都是先分类、再定排序键、最后统一输出。最后分享一个小技巧如果某个测试点始终过不去别急着猜隐藏条件试着把输出格式和排序键逐项和题目原句对一遍。很多时候问题出在“达到”“不低于”“相等时”这些字眼上而不是算法本身。把题目的每个限定词都圈出来代码里一定能找到对应的判断找不到的就是漏了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑