
刚考完第十六届蓝桥杯Java B组这两天的学习群里基本被前三题刷屏了。很多同学私信我“第一题样例明明过了提交0分”“第二题我用暴力跑的超时了但不知道错哪”“第三题到底该用int还是long我这个边界莫名其妙就WA”。说实话前3题在整张卷子里属于“保底分”的存在但恰恰是这种题最容易让人栽跟头因为代码短、思路浅反而让人放松了警惕。这篇就把我在复习和答疑过程中整理的典型疑问、踩坑点和修复思路一次性说清楚不一定只针对某一道原题但思路绝对同样适用。1. 先谈定位为什么前3题是“最容易翻车”的送分题这几年的蓝桥杯Java B组省赛第1、2、3题基本遵循一个规律第1题是签到题考的是读题和基础语法偶尔套个数学公式第2题是简单模拟或字符串处理可能会掺一点排序、计数第3题开始提难度常见套路是前缀和、二分、双指针或者一个比较隐蔽的边界判断。它们共同的特点是“不考高深算法考的是会不会踩坑”。很多同学备考时把精力全压在后面的贪心、搜索、动态规划上对前3题反而没怎么练结果真上了考场发现第1题卡了20分钟不敢交第2题暴力写完不知道复杂度能不能过第3题样例全对但一交就是红的。我把这些现象总结成三类疑问第一题是“不知道为什么错”第二题是“不知道能不能过”第三题是“不知道边界在哪”。搞清楚这三件事前三题基本就能拿稳。另外先说明一下原题题面各平台的回忆版本多少有出入我这篇更侧重把这类题背后共通的“坑”拆开讲。如果你现在手里正好有某道题没改出来对着下面的常见原因逐条查大概率能找到病根。2. 第1题的疑问样例全对提交却是0分问题出在哪2.1 你写的是“能跑的代码”不是“能得分的代码”第1题翻车率最高的原因往往不是算法而是流程。我见过太多同学在样例输入输出上测试没问题提交却0分最后发现是下面几种情况多打印了调试信息。有人在循环里System.out.println(当前到了哪一步)没删代码里遍布输出得分自然为零。输出格式和题目要求不一致。题目要求每个结果占一行你用了空格分隔题目要求输出“YES”你输出了“Yes”。别笑这些都是真实发生过的送命细节。没处理“多组数据”。比赛里很多题第一行给出T表示测试组数或者输入直到EOF结束你只按单组数据写第二组就会直接读错值。这类问题有个共同点本地跑得爽平台全红。原因也很简单——评测系统是按标准输入输出对拍的它不管你代码逻辑多漂亮只看输出字符串是否严格匹配。你哪怕多一个空格、多换一次行都算错误。2.2 一个典型签到题疑问拆解范围太大时for循环真的会超时假设第1题是“给定n和m求1到n之间所有能被m整除的数之和n最大到10^9”。很多同学的直觉是long sum 0; for (int i 1; i n; i) { if (i % m 0) { sum i; } } System.out.println(sum);这个写法思路没错样例大概率也能过但n到10^9时就会超时因为O(n)级别的遍历在现代评测机上也扛不住1亿次运算。正确做法是把能被m整除的数看成等差数列首项是m末项是n / m * m项数是n / m。long k n / m; // 项数 long sum m * k * (k 1) / 2; System.out.println(sum);这里还有一个隐藏问题m * k * (k 1)中间结果可能远超int范围所以必须用long。我在答疑中发现很多同学知道要优化成公式但偏偏觉得“n才10^9int能存下”结果中间乘积溢出变成负数。这个点我在后面第4部分还会细讲因为它也是第3题的高频错因。2.3 第1题防翻车清单提示第1题从读题到交卷建议控制在10分钟以内。一旦超过20分钟还没AC先停下来怀疑是不是掉进流程坑而不是死磕逻辑。我考试和训练时给自己定的检查顺序是这样的先读样例确认输入有几组、输出是什么分隔符。看n/m这类规模上限手算一下for循环能不能跑完。写代码时把所有无关输出删干净只保留标准输出。提交前用题目样例再跑一遍眼睛盯输出格式。如果WA立刻检查是不是该用long却用了int。这套流程看起来简单但真能拦住80%的低级失误。第一题本质是“让你拿分”不是一个需要展现天才写法的舞台。3. 第2题的疑问暴力枚举到底能不能过3.1 先算复杂度再决定要不要优化第2题比第1题多了一层“选择”我有简单粗暴的解法也有相对复杂的优化到底用哪个答案是看数据范围。蓝桥杯常见的几个量级对应关系我整理了一个表基本可以无脑参考数据规模可接受的复杂度常见算法选择n 100O(n^3)三重循环暴力n 1000O(n^2)两重循环、动态规划基础版n 10^5O(n log n)或O(n)排序、二分、前缀和、双指针n 10^7O(n)线性扫描、计数数组n 10^8需要数学公式或特殊优化找规律、等差/等比公式、快速幂注意Java比C慢不少同样O(n log n)在C能过的题Java在极限数据下可能超时。所以Java选手的常数优化很重要该用BufferedReader就别图省事用Scanner。这里有一个特别常见的疑问“我暴力写完了样例过了但担心超时要不要现场想优化”我的建议是如果估算复杂度在10^7以内直接提交别浪费时间如果明显超了先花几分钟想能不能降一维。考场最怕的不是不会优化而是暴力不能过却舍不得删最后硬交上去白白丢了AC机会。3.2 第二题常见的三个“隐形坑”很多同学第二题卡住不是卡在算法而是卡在一些“语法顺手但性能极差”的写法上。我归纳了三个高频问题坑一字符串拼接太慢。如果题目里要循环拼接字符串千万别用String str。Java的String是不可变对象每次拼接都会产生新对象大量拼接会让复杂度变成O(n^2)。正确做法是StringBuilder或StringBuffer。坑二用HashMap代替数组计数。统计字符频率这类题数据范围如果是小写字母或者数字直接用int[26]或int[10]别用HashMapCharacter, Integer。数组访问是O(1)而且常数极小HashMap不仅要拆箱装箱还要算哈希同样的逻辑下跑起来能慢出3倍以上。有人在第二题用TreeMap去排序更不建议因为TreeMap基于红黑树插入是O(log n)完全没必要。坑三排序时忽略Collections.sort和Arrays.sort的差异。对普通数组用Arrays.sort没问题对ArrayList用Collections.sort也正常但如果你对基本类型数组用自定义ComparatorJava会对基本类型装箱性能会明显下降。能直接用默认排序就别非要造轮子。3.3 一个典型第二题的疑问怎么优化暴力枚举假设题目类似“给定数组a求所有长度为k的连续子数组的最大和”。最直观的写法是穷举每个起点再内层累加复杂度O(n*k)。如果n和k都是10^5这个写法必挂。正确的优化思路是用前缀和把区间和变成O(1)查询int n ...; int k ...; long[] pre new long[n 1]; for (int i 1; i n; i) { pre[i] pre[i - 1] a[i - 1]; } long ans Long.MIN_VALUE; for (int i k; i n; i) { long sum pre[i] - pre[i - k]; // 区间 [i-k, i-1] 的和 ans Math.max(ans, sum); } System.out.println(ans);这个代码直接把复杂度从O(n*k)降到了O(n)。很多同学看完后问我“为什么pre要是long类型数组a不是int吗”因为区间和累加的时候多个int相加很容易超过2^31-1如果不提前用long一次溢出整个答案就全错了。学这个结论可以比别的题多记一句凡是一段区间累加前缀和数组直接开long不用想别的。4. 第3题的疑问边界条件和数据范围怎么防4.1 “用int还是long”一个字都不能差第三题有大量分数丢在数据类型上。Java的int上限是2147483647大概2.1×10^9。如果你看到一个数据约束是10^9级别但凡涉及乘法或累加就必须警惕。我经常用一个笨方法帮群友判断**把循环次数和数值上限同时看。**比如单个值是10^9单个操作不超出int但累加两次就出事。两数相乘各为10^5乘积就是10^10铁定溢出。取模情况下中间运算是10^12但结果小于10^9这时中间过程也必须用long不能在取模前用一个int变量承接乘法结果。很多人在第三题的WA原因其实一查就是乘法发生在int变量上然后强转long结果已经晚了。记住Java里运算结果的类型由运算数决定int * int产生的是int溢出了再赋值给long也没用。正确写法是在运算前就把其中一个数转成long比如(long) a * b % mod。4.2 取模运算的负数陷阱第三题如果涉及取模还有一个Java特有的经典坑%在Java中对负数结果会保留负号。比如-5 % 3在数学上通常定义为1但Java算出来是-2。如果题目的区间查询或前缀和涉及减法你有两种情况会踩到这个坑pre[r] - pre[l-1]算出来是负数因为前缀和取模后右边比左边小。希望得到非负余数结果输出一个负数直接WA。修复方式很简单在每次取模后加上模数再取一次模long result (pre[r] - pre[l - 1]) % MOD; if (result 0) result MOD;或者写成一行long result (pre[r] - pre[l - 1] MOD) % MOD;这里有个细节当差值很小时 MOD没问题但差值如果是负十倍百倍例如-800加MOD还是负数所以最稳妥的方式还是先%再判断后加MOD。4.3 第三题多见的WA原因修正示范我把答疑时看见的三道第三题高频问题列出来顺便给修复方案症状错误原因修复方式样例过大数据WAint溢出把相关累加/乘积变量改成long乘法前强转long结果一直是负数取模后减法产生负数负结果加MOD修正数组越界前缀和下标从0还是1没搞清统一用1-indexed数组长度n1循环从1开始二分死循环l和r收敛条件不对用while (l r)配合mid l (r - l) / 2第三题很考验“细心”而不是“灵感”。如果你能把所有边界情况都在写代码时提前标记出来而不是靠样例试错这个分数基本就握在手里了。5. 常见问题速查这些疑问你中了几个我把这两天被问到的典型问题统一汇总成一个表方便你按症状自查。如果你比赛时某道题卡了也可以拿来对照比反复问人高效得多。疑问原因正确做法第1题本地对提交0分输出多余字符或格式不符删除调试输出严格按题目要求输出第1题for循环超时数据规模大O(n)不可行用数学公式或O(log n)算法第2题暴力超时复杂度超过10^7换成前缀和、双指针、排序等降复杂度方案第2题字符串拼接超时String不可变导致O(n^2)用StringBuilder第2题排序结果不对Comparator写法有误或装箱损耗用默认排序或重写Comparable第3题用long还是int纠结没算清中间运算范围涉及乘法和累加一律long第3题取模结果负数Java % 对负数保留符号负结果补MOD修正第3题二分死循环mid取值和边界更新方向矛盾用mid l (r - l) / 2并检查收敛第3题数组越界0-indexed和1-indexed混用统一一种索引体系数组开够长度这里补充一个容易被忽略的“经验性技术点”Java选手读大量输入时Scanner虽然是新手最爱但它真的慢。如果题目输入的数据量达到10^5以上Scanner就可能有风险更稳妥的是用BufferedReader加StringTokenizerBufferedReader br new BufferedReader(new InputStreamReader(System.in)); StringTokenizer st new StringTokenizer(br.readLine()); int n Integer.parseInt(st.nextToken());这个方法看起来啰嗦但比赛时它能实打实帮你省下很多时间。很多人算法没问题最后的TLE是被输入输出拖累的这句话我说了很多次但每年还是有人不听。6. 赛后复盘前三题到底该怎么安排时间和心态6.1 时间节奏建议蓝桥杯Java B组共4小时前3题绝大多数选手的目标是保准、保快。我个人的建议节奏是第1题读题写验证合计不超过10分钟。如果5分钟还没思路多半是题目理解偏了停笔重读样例。第2题15到20分钟。其中前5分钟必须用来估算复杂度决定暴力还是优化不能写完再看数据范围。第3题30到40分钟。这个题可能需要尝试、推导、测试边界但也要设置止损点超过40分钟没进展就跳到第4题比赛不是单题马拉松。如果你是三题连做前面两题尽量交给“肌肉记忆”把脑力留给第3题。前两题其实是可以提前训练到不需要思考就能写对的。6.2 交卷前5分钟检查什么最后一个很实用的小习惯交卷前把每道题的代码从头到尾扫一遍重点看三样东西——是不是用了long、有没有删除调试输出、数组下标有没有越界可能。这五分钟做的事情经常比前面半小时死磕还值钱。6.3 一个真实体会打完第十六届这场我个人最大的感受是前3题拉开差距的地方不在灵光一现而在“该用long的地方没用long该优化复杂度的时候没优化该删调试输出的时候没删”。准备下一场比赛的同学与其刷一堆难题不如先把近5年的省赛前3题拿出来每一道都按“数据范围、复杂度、边界、输出格式”四个维度复盘一遍。这个方法看起来土却是最稳的。最后再分享一个小技巧也是我自己解题时一直用的对一道题的“所有疑问”做分类标记——是输入输出问题、是性能问题、还是边界问题。定位清楚之后修复只是几分钟的事。希望这篇能帮你把这次比赛的前3题疑问一次理清。