资讯详情

编程入门必刷题:打分系统中的数组遍历与精度控制

📅 2026/10/28 16:02:28 | 华诺云谱 👁 阅读
编程入门必刷题:打分系统中的数组遍历与精度控制
1. 项目概述一道看似简单的打分题藏着编程入门最硬的几块敲门砖“【洛谷】P5726 【深基4.习9】打分”——这个标题在刷题平台里毫不起眼甚至可能被初学者一眼略过不就是输入几个分数、去掉一个最高一个最低、算个平均数吗太简单了。但我在带某高校编程实训班时连续三届学生都在这道题上卡住超过40分钟有人输出结果多了一位小数有人删错了数却没发现还有人用排序删头尾结果把所有相同分数全删了。它根本不是“算术题”而是一道精准设计的编程思维压力测试题考察你是否真正理解“输入→处理→输出”的完整闭环是否能区分“逻辑正确”和“结果精确”是否在写代码前就预判了边界情况。核心关键词——数组遍历、极值查找、浮点精度控制、数据清洗意识——全部浓缩在这不到20行的题目描述里。它适合刚学完循环和数组、正准备接触函数与模块化思维的初学者也适合想检验自己基础是否扎实的转行者。别被“深基4.习9”的编号骗了这道题的实操价值远超同章节其他题目它逼你亲手处理真实世界的数据噪声——比如评委打分时的主观偏差、录入错误、极端值干扰。我后来把这道题拆解成课堂案例专门讲“如何让程序像人一样思考异常”学生反馈比直接讲排序算法印象更深刻。2. 题目需求深度拆解为什么“去掉一个最高分和一个最低分”不是一句废话2.1 表面要求与隐藏陷阱的对照分析题目原文描述通常简洁“输入n个评委的打分整数去掉一个最高分和一个最低分求剩余分数的平均值保留两位小数”。但这句话里埋着三层需要主动识别的“非显性需求”第一层数据规模与输入方式n的范围是3≤n≤100这意味着你不能假设只有3个数此时删掉头尾只剩1个也不能忽略n100时的性能——虽然O(n)遍历完全够用但若有人图省事用sort()再切片就得意识到sort()在n100时虽快却引入了不必要的额外空间开销和逻辑冗余。更重要的是输入是“一行n个整数”而非逐行输入这直接决定了你必须用split()配合map()或列表推导式一次性读取而不是写个for循环反复input()。第二层“去掉一个最高分和一个最低分”的精确语义这是绝大多数初学者栽跟头的地方。题目强调“一个”意味着若有多个相同最高分如[95,95,85,75]只删第一个出现的95还是删最后一个题目没说但标准做法是删除任意一个即可因为数学上“去掉一个最高分”只要求移除一个实例不影响剩余数据集的统计意义。但关键在于不能删除所有最高分或所有最低分。曾有学生写scores.remove(max(scores))结果当最高分重复时remove()只删第一个匹配项看似正确可如果他接着写scores.remove(min(scores))而此时原最低分在删除最高分后位置变动min(scores)可能已变导致删错数。更隐蔽的错误是先sort()再scores scores[1:-1]这在[95,95,85,75]中会删掉第一个95和75剩下[95,85]结果正确但在[95,85,75,75]中会删掉95和最后一个75剩下[85,75]而本该删的是95和第一个75剩下[85,75]——碰巧对了但逻辑是错的。真正的安全做法是独立查找索引并删除或不修改原数组用条件过滤。第三层输出精度的强制约束“保留两位小数”不是四舍五入的建议而是格式化要求。很多学生用round(avg, 2)结果遇到95.333...时输出95.33看似对但若计算结果是95.0round(95.0, 2)返回95.0而非题目要求的95.00。Python的round()返回的是数字类型而题目输出需要字符串格式xx.xx。必须用格式化字符串如f{avg:.2f}它强制补零且遵循银行家舍入法对5的处理更公平这才是生产环境的标准做法。提示这道题的测试用例必然包含边界数据——n3删后只剩1个数、n100大数组压力、最高分/最低分重复如[100,100,90,80,70,70]、分数全相同如[85,85,85,85]。不提前想清楚这些调试时会陷入“本地对、平台错”的死循环。2.2 为什么不用内置函数max()/min()直接删——一次真实的课堂实验我让两组学生分别实现A组用max()/min()找值后remove()B组用单次遍历同时记录最大值、最小值及其索引。结果A组在测试用例[10, 10, 5, 5]上失败——他们先remove(max(scores))删掉第一个10数组变成[10,5,5]此时min(scores)是5remove(5)删掉第一个5剩下[10,5]平均值5.0但正确做法应删一个10和一个5剩下[10,5]结果相同看似没问题。问题出在另一组数据[10, 5, 10, 5]A组删第一个10后为[5,10,5]再删min5删掉第一个5剩下[10,5]而B组通过索引删除删的是索引0的10和索引1的5剩下[10,5]——结果一致。但关键差异在稳定性A组依赖remove()的“第一次匹配”行为而题目未规定删除哪个实例平台测试机可能用不同Python版本remove()行为一致但逻辑上不健壮。B组明确指定索引行为完全可控。我在课堂上用timeit对比对n100的随机数组A组平均耗时12.3μsB组8.7μsB组更快且逻辑清晰。这印证了一个经验在基础题中优先选择时间复杂度明确、副作用少、逻辑路径单一的方案哪怕多写几行代码。2.3 真实场景映射这不是模拟题是简化版的“赛事评分系统”别把它当成孤立的练习。某省级青少年科创大赛的初评系统核心逻辑就是这道题的工业级放大版输入50名专家对100个项目打分0-100分整数清洗每个项目去掉1个最高分、1个最低分防专家个人偏好计算剩余48个分的平均值按降序排名输出排名平均分精确到0.01他们最初用Excel手动操作错误率高达17%改用脚本后关键改动就是把“删最高最低”从sort()slice换成索引定位删除错误率降至0.2%。区别在哪sort()会改变原始顺序而索引删除保持了数据时序——这对后续审计“谁打了多少分”至关重要。所以P5726的“删一个”不是抠字眼而是训练你建立数据操作可追溯性的意识。你在洛谷AC的代码稍作扩展就能跑在真实赛事后台。3. 核心实现方案对比与选型依据三种解法的实战成本分析3.1 方案一暴力排序切片法新手首选但有隐患这是最直观的解法读入数组→排序→切片去掉首尾→求平均。n int(input()) scores list(map(int, input().split())) scores.sort() cleaned scores[1:-1] # 直接切片 avg sum(cleaned) / len(cleaned) print(f{avg:.2f})优势代码短7行符合直觉初学者5分钟内能写出。隐患与修复隐患1修改原数组。sort()是原地排序若后续还需用原始顺序如记录哪位评委打了多少分数据已毁。修复用sorted(scores)生成新列表。隐患2重复值误删。如前所述[10,10,5,5]排序后为[5,5,10,10][1:-1]得[5,10]正确但若题目要求“保留原始顺序下的中间段”此法失效。不过P5726无此要求可接受。隐患3n3时边界。[1,2,3]切片[1:-1]得[2]长度1sum([2])/12.0f{2.0:.2f}2.00完美。实操心得此方案在洛谷100%通过但我在教学中会要求学生加一行注释# 注意此法隐含假设——最高/最低分各只有一个实例或多个实例时删除任意一个均可接受。写注释的过程就是在固化对题目语义的理解。3.2 方案二单次遍历找极值索引稳健之选推荐主力不排序只遍历一次数组同时记录最大值、最小值及其首次出现的索引。n int(input()) scores list(map(int, input().split())) max_val scores[0] min_val scores[0] max_idx 0 min_idx 0 for i in range(1, n): if scores[i] max_val: max_val scores[i] max_idx i if scores[i] min_val: min_val scores[i] min_idx i # 构建新列表跳过max_idx和min_idx cleaned [] for i in range(n): if i ! max_idx and i ! min_idx: cleaned.append(scores[i]) avg sum(cleaned) / len(cleaned) print(f{avg:.2f})优势时间复杂度最优O(n)仅两次遍历一次找索引一次构建新列表比排序O(n log n)更高效。逻辑绝对清晰明确指定删哪个位置无歧义。可扩展性强若题目升级为“去掉两个最高分”只需维护一个大小为2的最大堆而非重写逻辑。注意点当max_idx min_idx即n1但题目n≥3不可能或n2题目不允许无需处理。若最高分和最低分在同一位置不可能因n≥3且分数为整数除非所有分相同如[5,5,5]此时max_idx和min_idx可能相同都为0cleaned会漏删一个数。修复在构建cleaned时用计数器确保只跳过两个索引。更稳妥写法# 替代cleaned构建逻辑 to_remove {max_idx, min_idx} # 用集合自动去重 cleaned [scores[i] for i in range(n) if i not in to_remove]这样即使max_idx min_idx理论上n≥3时不会发生但防御性编程集合{max_idx, min_idx}大小为1只删一个数但题目要求删两个所以需保证max_idx ! min_idx。可在找索引后加断言assert max_idx ! min_idx, n3时最高最低分索引必不同。3.3 方案三函数式过滤法Pythonic但需警惕陷阱利用filter()和lambda或列表推导式基于值过滤。n int(input()) scores list(map(int, input().split())) max_score max(scores) min_score min(scores) # 关键只删一个最高分和一个最低分不是所有 # 先标记要删的索引 remove_idx set() # 找第一个最高分索引 for i in range(n): if scores[i] max_score: remove_idx.add(i) break # 找第一个最低分索引 for i in range(n): if scores[i] min_score: remove_idx.add(i) break cleaned [scores[i] for i in range(n) if i not in remove_idx] avg sum(cleaned) / len(cleaned) print(f{avg:.2f})优势代码更紧凑体现函数式思维。陷阱max()/min()本身是O(n)找索引又是O(n)总O(n)但常数因子比方案二大。若不加break会找到所有极值索引remove_idx变大删多数据。必须用break确保只取第一个。实操心得我见过学生用scores.remove(max_score)两次结果第二次remove()因max_score已不存在而报错。方案三用索引标记彻底规避了remove()的副作用是更安全的“函数式”实践。4. 完整实操流程与关键参数详解从零开始跑通每一个环节4.1 环境准备与输入解析为什么input().split()不能少洛谷的输入格式是标准的“一行n下一行n个空格分隔的整数”。必须严格匹配n int(input().strip()) # .strip()防首尾空格 scores_input input().strip() if not scores_input: # 防空行 scores [] else: scores list(map(int, scores_input.split()))参数详解input().strip()去除换行符和空格避免 95 85 75 解析出空字符串。split()默认按任意空白字符分割比split( )更鲁棒后者遇多个空格会出错。map(int, ...)将字符串转整数比列表推导式[int(x) for x in ...]内存更优返回迭代器。常见错误写n input()不转int导致后续range(n)报错。写scores input().split()不转intsum(scores)报错字符串不能求和。忘记list()包裹map()在Python3中map返回迭代器len()会报错。注意洛谷测试机环境是Linuxinput()自动处理换行无需sys.stdin.readline()过度优化反而增加复杂度。4.2 数据清洗核心环节三步走确保万无一失清洗不是一步到位而是分三步验证步骤1确认n合法if n 3 or n 100: raise ValueError(n must be between 3 and 100) # 洛谷不需但本地调试建议加步骤2找极值索引方案二主干max_idx 0 min_idx 0 for i in range(1, n): if scores[i] scores[max_idx]: # 用scores[i]比较非max_val变量 max_idx i if scores[i] scores[min_idx]: min_idx i为什么用scores[i] scores[max_idx]而非scores[i] max_val因为max_val需同步更新而用索引直接访问避免变量同步错误。实测中有学生写max_val scores[i]后忘更新max_idx导致后续删除错误索引。步骤3构建清洗后数组# 用集合确保索引唯一 indices_to_remove {max_idx, min_idx} cleaned [] for i, score in enumerate(scores): # enumerate更Pythonic if i not in indices_to_remove: cleaned.append(score)关键计算len(cleaned)应为n-2可加断言assert len(cleaned) n - 2, fExpected {n-2}, got {len(cleaned)}。这在调试时能立刻暴露逻辑错误。4.3 平均值计算与精度控制.2f背后的舍入原理计算本身简单avg sum(cleaned) / len(cleaned)。但精度控制是重点为什么不用round(avg, 2)round(2.675, 2)在Python中返回2.67而非2.68因为浮点数存储误差和银行家舍入法偶数舍入。而f{2.675:.2f}输出2.68更符合日常预期。洛谷测试用例用的是标准printf风格格式化必须用f-string或format()。.2f的完整含义.表示小数点2表示小数位数f表示定点浮点数格式自动补零f{5:.2f}→5.00实测对比值round(x,2)f{x:.2f}95.33333395.3395.3395.095.095.0095.33595.3395.34可见f-string才是题目要求的“保留两位小数”的唯一正确解。4.4 完整可运行代码与本地测试用例# P5726 打分 - 最终版方案二 n int(input().strip()) scores list(map(int, input().strip().split())) # 边界检查非必需但推荐 if n 3 or n 100: exit() # 单次遍历找最高分和最低分索引 max_idx 0 min_idx 0 for i in range(1, n): if scores[i] scores[max_idx]: max_idx i if scores[i] scores[min_idx]: min_idx i # 构建清洗后列表 indices_to_remove {max_idx, min_idx} cleaned [scores[i] for i in range(n) if i not in indices_to_remove] # 计算平均值并格式化输出 avg sum(cleaned) / len(cleaned) print(f{avg:.2f})本地测试用例输入13\n95 85 75→ 输出85.00删95和75剩85输入25\n100 100 90 80 70→ 输出90.00删一个100和70剩100,90,80 → avg90输入34\n85 85 85 85→ 输出85.00删一个85和一个85剩两个85 → avg85运行命令python p5726.py test1.in用重定向测试。5. 常见问题与排查技巧实录那些年踩过的坑和救急方案5.1 典型错误速查表错误现象可能原因排查方法修复方案答案错误WA用了sort()后[1:-1]但n3时[1:-1]为空列表打印len(cleaned)看是否为n-2改用索引删除或加if n2: ...保护运行错误REscores.remove(max_score)时max_score不在列表中已删过在remove前加if max_score in scores:改用索引删除彻底规避格式错误PE输出95.0而非95.00用print(repr(avg))看实际值再print(f{avg:.2f})强制用f{avg:.2f}禁用round()时间超限TLE对n100用嵌套循环如双重for找极值用timeit测find_max_min()耗时改单次遍历O(n)解决5.2 调试黄金三招招一打印中间状态在关键步骤后加print(fn{n}, scores{scores}, max_idx{max_idx}, min_idx{min_idx}, cleaned{cleaned})。不要怕输出多洛谷不显示你的print只看你最终print。本地调试时这行能让你秒懂哪步出错。招二用小数据穷举验证写个脚本自动生成所有n3的组合如[1,2,3],[1,1,2],[5,5,5]跑一遍看输出是否符合预期。我常用itertools.product([1,2,3], repeat3)生成测试集。招三反向验证逻辑假设输出是85.00反推cleaned应为[85]或[84,86]等再看原始scores是否能通过删一个最高一个最低得到该cleaned。这招在WA时特别有效能快速定位是删错数还是算错平均。5.3 进阶避坑技巧来自某公司代码评审的真实教训技巧1永远用enumerate代替range(len())错误for i in range(len(scores)): score scores[i]正确for i, score in enumerate(scores):原因少写len()避免索引越界且更易读。某公司代码评审中range(len())被标记为“不Pythonic”要求整改。技巧2用set管理待删索引而非列表错误to_remove [max_idx, min_idx]然后if i in to_remove:O(n)查找正确to_remove {max_idx, min_idx}if i in to_remove:O(1)查找原因当n增大效率差异明显。n100时列表in平均查50次集合查1次。技巧3对输入做防御性检查try: n int(input().strip()) scores list(map(int, input().strip().split())) assert len(scores) n, fExpected {n} scores, got {len(scores)} except (ValueError, AssertionError) as e: print(Input error) exit()这在本地调试时能捕获输入格式错误避免在洛谷上因格式问题WA。我在某次企业内训中让工程师用这道题写代码30人中有7人用了sort()slice其中3人在代码评审时被要求重构为单次遍历——理由是“排序引入了不必要的计算复杂度且不符合数据清洗的语义清晰性”。这道题的价值正在于它用最简场景逼你做出工程级的权衡。6. 拓展思考与能力迁移这道题如何成为你技术成长的支点6.1 从打分到真实系统的演进路径P5726是起点不是终点。它的核心模式可平滑迁移到电商评分系统用户对商品打分1-5星去掉1个最高1个最低计算可信度得分。此时分数是浮点数如4.5需处理小数输入map(float, ...)替代map(int, ...)。传感器数据清洗IoT设备每秒上报温度剔除异常值最高/最低计算滑动窗口平均值。此时需用队列collections.deque维护最近n个值实时更新极值。机器学习特征工程对数值型特征做离群值处理常用IQR四分位距法但“删最高最低”是最简化的离群值过滤是理解更复杂方法的基石。6.2 编程思维的质变从“解题”到“建模”做完这道题你应该能回答如果题目改为“去掉k个最高分和k个最低分”代码如何改答维护大小为k的最大堆和最小堆如果分数是浮点数且要求保留三位小数格式化字符串怎么写答f{avg:.3f}如果评委打分有权重如专家权重2普通评委权重1如何加权平均答sum(score * weight for ...) / sum(weights)这些问题的答案不再靠背模板而是基于你对“输入-处理-输出”链条的肌肉记忆。某导师曾说“当你看到新题第一反应不是想‘用什么函数’而是问‘数据从哪来、要变成什么样、中间要经历什么步骤’你就毕业了。”P5726正是这个毕业典礼的入场券。6.3 个人实操体会为什么我坚持手写索引查找我试过所有方案最终在教学和工作中固定用方案二单次遍历索引。原因很实在调试成本最低max_idx和min_idx两个变量打印出来一目了然删哪个数清清楚楚。修改成本最低客户突然说“改成去掉两个最高分”我只需加一个second_max_idx变量逻辑几乎不变。心理成本最低写完不用猜“会不会有边界问题”因为每一步都可控。这道题教会我的不是某个算法而是在简单任务中建立严谨习惯。就像木匠第一课不是做家具而是磨好每一把凿子。你在洛谷AC的那一刻收获的不仅是Accepted更是面对任何数据处理任务时那种“我知道每一步会发生什么”的笃定感。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑