
每年GESP二级的题目里二维数组都是重头戏。今年3月的“等差矩阵”看起来是个数学名词实际考察的是你对数组下标的掌控力。很多学生考完说明明每一行都判断了怎么还是错问题多半出在列判断的循环写法上。这篇文章把这个题从头到尾拆一遍同时把二级考生最该练的调试习惯也带上希望能帮到正准备考GESP的同学。1. 题目到底在问什么1.1 还原题目描述在2025年3月的GESP二级考试中“等差矩阵”是一道典型的二维数组应用题。题目背景差不多是这样输入两个正整数 n 和 m然后输入一个 n 行 m 列的整数矩阵。如果矩阵的每一行都构成等差数列并且每一列也都构成等差数列那么称这个矩阵为等差矩阵。要求程序输出“Yes”或“No”。为什么题目会这样设计因为二级大纲明确要求掌握二维数组的嵌套遍历而“等差数列”定义里的“相邻两项差值相等”需要你反复比较相邻元素。题目本身不涉及高深算法却能把二维数组的基本功考得明明白白。这里要特别说清楚什么是“每一行都是等差数列”比如一行数据是 3 5 7 9相邻差都是 2那它就是等差数列如果一行是 1 2 4相邻差分别是 1 和 2就不满足。列也是这样。所以判断的核心就是在一行中把相邻元素做差检查所有差值是否相等在列中也做同样的工作。注意行判断和列判断是相互独立的两个条件都满足才算“Yes”。1.2 考点拆解与难度评估从考点来看这道题至少覆盖了四个方面。第一是二维数组的定义与输入。你需要会用 int a[105][105] 声明矩阵会用双层循环读入数据。很多新手在这个环节反而出问题下标顺序写反会导致读入的数据错位后面判断自然全错。第二是循环边界。判断一行时元素下标从 0 到 m-1判断相邻差时循环变量一般只能走到 m-2否则 a[j1] 就会越界。列同理这要求你非常清楚数组的长度和下标范围。第三是标志变量的使用。在判断过程中一旦发现某一组差不相等就可以判定失败。但你通常不能立刻输出 No因为可能后面还有更多判断你需要用一个 bool ok 记录结果最后统一输出。这就会涉及 break 和布尔变量的配合。第四是边界情况处理。当 n1 或 m1 时矩阵只有一行或一列。按数学定义单个元素、两个元素的序列都可以看成等差数列因为不存在“互相矛盾的差值”。如果你不特殊处理直接取 a[0][1] - a[0][0]就会数组越界。这个边界点是很多同学丢分的地方。难度上这道题在二级里属于中等偏上。它没有复杂的算法但代码细节非常多。如果能用函数把行判断和列判断分开逻辑会清爽很多复查也方便。2. 判断等差的两种思路暴力遍历和函数化拆分2.1 思路一一次性双层循环硬判断最直观的思路是先依次判断每一行再依次判断每一列。判断第 i 行时先取出该行前两个元素的差作为基准公差 d a[i][1] - a[i][0]。然后从第二对相邻元素开始逐一比较 a[i][j1] - a[i][j] 是否等于 d。只要发现一次不等于就说明这一行不是等差数列整个矩阵可以直接判定为 No不需要再继续。判断列时也一样。列方向上的元素下标变化是行标所以需要固定列号 j让行号 i 从 0 到 n-1 移动。比较的是 a[i1][j] - a[i][j] 与基准公差 d。这一步的注意点是外层循环应该按列遍历而不是按行遍历。这个细节看起来简单但在考场紧张时特别容易写反。2.2 思路二用函数把行判断和列判断拆开我更喜欢用函数把单行判断和单列判断拆开。原因是函数内部可以随时 return false避免了多层循环里 break 只跳出内层的尴尬函数命名还能让代码一读就懂方便考试复查。bool rowOk(int r) 接收行号返回这一行是否等差。bool colOk(int c) 接收列号返回这一列是否等差。主函数里只需要bool ok true; for (int i 0; i n; i) { if (!rowOk(i)) { ok false; break; } } if (ok) { for (int j 0; j m; j) { if (!colOk(j)) { ok false; break; } } }这样写每一步的意图都很清楚。函数拆分的额外好处是方便单独测试你可以构造几组行数据先验证行判断函数对不对再验证列判断。如果考场上时间紧张不写函数也可以但逻辑千万别乱。2.3 为什么推荐先判断行、再判断列先判断行、再判断列顺序并不影响正确性。但面对测试数据时行判断通常更直观不容易错。如果先判断列一旦列循环写错了排查起来要花更多时间。我在模拟考试中见过不少同学先写列判断结果列号写成了行号白白浪费二十分钟。另一个细节是需要先检查 n 和 m 是否为 1 或 2。如果 n2那么每一列都自动满足等差条件因为任意两个数的差只有一组不存在“与其他组不一致”的情况。m2 时同理。这个判断最好放在函数头部简洁又安全。2.4 复杂度分析每个元素最多参与两次比较一次作为行差分的左侧元素一次作为列差分的左侧元素。所以时间复杂度是 O(n*m)。n 和 m 通常不超过 100这个复杂度完全没问题。真正的耗时可能在于输入输出所以建议在 main 开头加 ios::sync_with_stdio(false) 提速不过二级数据量小不加速也没关系。3. 完整C代码与逐段讲解3.1 建议的代码结构下面给出一个可以直接抄的解法。数组大小按题目的常见上限 105×105 开读入 n、m再读入矩阵。两个判断函数都放在 main 前面main 里调用。#include iostream using namespace std; int a[105][105]; int n, m; bool rowOk(int r) { if (m 2) return true; int d a[r][1] - a[r][0]; for (int j 1; j m - 1; j) { if (a[r][j 1] - a[r][j] ! d) return false; } return true; } bool colOk(int c) { if (n 2) return true; int d a[1][c] - a[0][c]; for (int i 1; i n - 1; i) { if (a[i 1][c] - a[i][c] ! d) return false; } return true; } int main() { ios::sync_with_stdio(false); cin.tie(NULL); cin n m; for (int i 0; i n; i) { for (int j 0; j m; j) { cin a[i][j]; } } bool ok true; for (int i 0; i n; i) { if (!rowOk(i)) { ok false; break; } } if (ok) { for (int j 0; j m; j) { if (!colOk(j)) { ok false; break; } } } cout (ok ? Yes : No) endl; return 0; }3.2 代码逐段解读先看 rowOk 函数。如果 m 2直接 return true。为什么m1 时只有一个元素没有相邻项可比m2 时只有一组相邻差它和它自己当然相等。如果 m3先让 d 等于这一行前两个元素的差。接着循环 j 从 1 到 m-2。当 j1 时比较 a[r][2] - a[r][1] 是否等于 d。如果不等于说明这一行从第二个位置开始就不连续等差整行失败。为什么从 1 开始因为第 0 和第 1 个元素的差已经存到 d 里了我们要从第二对差分开始比较。你也可以选择从 j2 开始比较 a[r][j] - a[r][j-1]但逻辑要保持一致。colOk 函数同理只是把下标反过来。注意执行到 colOk 时n 才是行的数量。d a[1][c] - a[0][c] 比较的是同一列上相邻两行。主函数的流程用一句话概括先读入然后行循环检查一旦失败就置 okfalse 并 break若行全部通过再列循环检查。最后 cout (ok ? Yes : No) endl。三元运算符在这里很简洁如果不熟也可以写 if (ok) cout Yes; else cout No;。3.3 两个备选写法有些同学喜欢不用函数把所有逻辑放进 main。比如可以先判断所有行bool ok true; for (int i 0; i n ok; i) { if (m 2) { int d a[i][1] - a[i][0]; for (int j 1; j m - 1; j) { if (a[i][j 1] - a[i][j] ! d) { ok false; break; } } } }这种写法不需要额外函数但要注意外层循环条件写 i n ok可以让行判断在失败后直接退出非常方便。不过代码嵌套一多容易看晕。还有一种更“暴力”的写法先把每一行每一列的差值都算出来存到一个一维数组里再看所有差值是否相等。这种方法代码更长而且需要额外空间不推荐。3.4 用几个典型数据检验代码自己写完后一定要在本地跑几个用例。比如第一组3 3 1 2 3 2 4 6 3 6 9输出 Yes。第二组2 3 1 2 3 4 5 7输出 No因为第二行 4 5 7 不是等差。第三组1 5 1 3 5 7 9输出 Yes。第四组3 1 1 2 3输出 Yes。第五组3 3 1 2 3 2 4 6 4 8 12输出 No。第三行 4 8 12 的差是 4、4行是等差的但第一列 1 2 4 的差是 1、2列不等差所以整体 No。这个用例能精准测出列判断有没有写对。如果这些用例全部通过代码基本没大问题。注意输出大小写不要写成“YES”或“yes”。GESP 判题系统对字符串比对是大小写敏感的哪怕多一个空格都可能判错。4. 常见问题与避坑实录这一章集中讲我在带学生做这道题时遇到的高频错误。有些错误出现在“代码好像写完了”的时候非常坑。4.1 数组越界a[j1] 访问到了哪里如果行判断写成 for (int j 0; j m; j)在最后一个下标 m-1 时代码会去访问 a[r][m]。二维数组在内存中是连续排列的a[r][m] 实际会访问到下一行的第一个元素如果还有下一行的话或者访问到未定义内存。在评测环境里越界可能不报错但会得到随机数据导致答案错误。所以循环边界一定要记得写 j m - 1 或 j m - 2。4.2 在循环内部动态更新公差有同学这样写int d a[r][1] - a[r][0]; for (int j2; jm; j) { if (a[r][j] - a[r][j-1] d) ... } 这样是对的。但另一些人写成int d; 循环里每次判断之前都重新给 d 赋值为当前差分那结果恒为 true等于没判断。原因很简单每一次拿差分和它自己比较当然相等。正确做法是只在一开始取一次基准公差之后所有差分都和这个基准比较。4.3 列判断的外层循环写成了行遍历经典错误代码for (int i 0; i n; i) { int d a[i][1] - a[i][0]; for (int j 1; j m - 1; j) { if (a[i][j 1] - a[i][j] ! d) ok false; } }这段代码看起来没问题实际上还是在判断每一行根本没有判断列。列判断必须固定列号 c让行号变化。所以外层循环变量应该代表列号内层循环变量代表行号。4.4 忘记了 n1 或 m1 的极端情况很多教材不会专门讲这种边界但GESP很喜欢在测试数据里放。比如 1 3 和一行数据 1 2 3如果 rowOk 函数一上来就取 a[0][1] - a[0][0]没问题但如果 m1取 a[0][1] 就越界。解决方法是函数开头先判断长度是否小于等于 2是就直接返回 true。对这个题来说长度 1 或 2 都不需要做差分比较。4.5 break 只退出内层循环导致后续误判如果只在 main 里用双重循环判断且出现了 break很多同学以为 break 会退出所有循环结果实际只退出了当前内层循环外层循环还会继续跑下去。比如for (int i 0; i n; i) { bool okRow true; for (int j 1; j m; j) { if (a[i][j] - a[i][j - 1] ! d) { okRow false; break; } } if (!okRow) { // 这里只跳出了内层外层 i 还会增加 ok false; } }如果后面继续执行ok 可能会被后面的行覆盖。最清晰的办法是发现失败后直接置 okfalse并在外层循环条件里加 ok或者干脆用函数直接 return。4.6 输出格式的坑题目要求输出“Yes”或“No”注意首字母大写其余小写。有些同学写成了“YES”这样即使逻辑全对也会被判为错误。输出字符串时不要带多余空格或换行最好只输出一个字符串后再换行。4.7 调试小技巧考试的时候如果找不到错可以在循环里加一行 cout d endl; 看看公差是否符合预期。判断列时可以用一个嵌套循环临时输出整个矩阵确认输入没有读错。调试代码记得最终删除否则会影响输出导致大量错误。4.8 一个自制测试用例生成器想多练的同学可以写一个随机矩阵生成器再和暴力算法对拍。但二级阶段可能没学过对拍最简单的做法是手写几个边界用例全部跑通后再交。上面的 3×3、2×3、1×5、3×1、以及全等矩阵基本覆盖了高频错误。为了更直观我把高频错误整理成一张速查表错误现象可能原因解决办法程序崩溃或返回随机答案访问 a[j1] 越界循环边界写 j m-1输出恒为 Yes循环里反复给 d 赋值d 在循环外只初始化一次列判断失效外层循环用行号遍历外层固定列号 c内层变行号 i边界数据报错未处理 n1 或 m1函数开头 if (m2) return true输出写成 YES字符串比较大小写敏感严格写 Yes / No5. 从等差矩阵延伸二级二维数组题的通用套路如果你把这道题吃透了会发现GESP二级里的很多二维数组题都是同一个套路输入一个矩阵然后按行或按列去检查某种性质。常见的有判断每行是否递增、统计每列的最大值、计算对角线元素和、判断矩阵是否对称等。等差矩阵只是把“递增”换成了“相邻差相等”本质上都是固定策略的遍历。5.1 二维数组的两种遍历模式第一种是行优先遍历外层循环控制行内层循环控制列。大多数题目都是先按行输入所以这种遍历很自然。第二种是列优先遍历外层循环控制列内层循环控制行。等差矩阵的列判断就必须用这种模式。很多同学不熟悉第二种模式导致卡壳。建议平时就用一个小矩阵反复练习外层 j 从 0 到 m-1内层 i 从 0 到 n-1访问 a[i][j]这就是列优先。5.2 “基准值 比较”的思想贯穿很多题这道题里我们先把第一个差分作为基准再用它和后面所有差分比较。类似的思想也出现在“判断数组是否全部相同”“判断字符串是否回文”“找出最大差值”等题目中。你可以边做题边总结一个模板取出基准值 → 遍历后续元素 → 一旦与基准不一致就返回假 → 全部一致才返回真。这个模板在二级阶段非常实用建议形成肌肉记忆。平时看到“判断是否满足某种一致性”的题都可以往这个模板上靠。5.3 考试时间分配与做题顺序GESP二级考试一般有两道编程题时间相对宽松但考场紧张容易慌。建议按这个顺序来先把题目读两遍确认矩阵大小、行列定义、输入顺序再在草稿纸上写一个输入样例手动算一遍结果然后写代码写完一定先用自己样例测试再想边界数据最后检查输出格式。我个人的习惯是判断类题目先处理极端输入比如 n1 或 m1再写主逻辑。因为极端情况最容易让程序崩溃如果评测系统因此返回运行时错误那比答案错误还要可惜。5.4 后续可以再练哪些题想再多练手可以尝试判断一个矩阵是否每行每列都有序递增或递减。提取矩阵的每一列输出每列的最小值。判断矩阵是否关于主对角线对称。计算两条对角线的和并比较。给定三个数判断能否构成等差数列。这些题和等差矩阵的考察点高度重合。练熟之后你会发现二维数组不再可怕。最后再分享一个考场小技巧如果对自己的列判断没有信心直接在草稿纸上画一个 2 行 3 列的矩阵模拟按行和按列的下标变化。比如 a[0][0]、a[0][1]、a[0][2] 和 a[1][0]、a[1][1]、a[1][2]列判断时你关心的是 a[0][0] 和 a[1][0] 是否在同一列所以必须保证内层循环里行号在变列号不变。画一次就记住了。