C++螺旋矩阵生成全攻略:两种核心算法与边界细节解析
发布时间:2026/10/2 11:35:10来源:尧图网络
C的环形矩阵填充学名叫螺旋矩阵生成是个看起来简单、上手就碎的问题。我第一次在这上面翻车是大二写数据结构作业脑子里的思路特别清楚从外到内一圈一圈填可一到代码层面就开始越界、覆盖、死循环。那个晚上我印象很深一边调一边怀疑自己是不是不适合写代码。后来在蓝桥杯省赛和两场技术面试里我又碰到了几乎一样的题才彻底想明白这类题考的根本不是智商而是对循环边界条件的掌控力。这篇文章的核心是一套可以直接抄走的C源码外加我把它们拆碎之后的理解。边界收缩法和方向向量法我都写了完整可编译的版本并且在g 11和Visual Studio 2022下都实测过。适合准备刷题面试的朋友、刚学完数组和循环想练手的新手以及想搞清楚二维vector到底怎么管理内存的人。读完你不仅能写出环形填充还能顺手搞定逆时针、矩形螺旋、中心向外螺旋这些变形题。1. 先搞明白环形矩阵到底在折腾什么1.1 题目到底要求我们做什么环形矩阵最经典的版本是这样给定一个正整数n生成一个n乘n的矩阵从左上角第一个元素开始按照顺时针方向依次填入1到n的平方。比如n等于3时结果是这样1 2 3 8 9 4 7 6 5n等于4时结果是这样1 2 3 4 12 13 14 5 11 16 15 6 10 9 8 7注意看规律先向右走到头再向下走到头然后向左走到头最后向上走到头完成一圈。下一圈往里缩再重复同样四个方向直到所有格子填满。要求填充意味着我们要写入矩阵的每一个位置。这和打印是两回事——打印只需要遍历输出填充需要先构造数据再输出。很多初学者把这两者混在一起结果一边填一边输出逻辑理不清。我建议严格分开先完整生成矩阵再单独写一个打印函数。这样每一步都能独立验证。这里有一个容易忽略的细节数字的递增方向是右上到左下再到左上整体是顺时针螺旋。如果把方向反了就成了逆时针螺旋这是最常见的变形题之一后面我会单独讲。1.2 为什么这道题到处都能碰到环形矩阵可以说是算法题里的流量明星。学习阶段它是嵌套循环和二维数组的绝佳练习题比九九乘法表有挑战性但又不像动态规划那样劝退。比赛阶段蓝桥杯、ACM校赛、高校算法课上都能看到它的身影经常作为基础题的压轴或者进阶题的前置。面试阶段它是考察候选人能不能把思路转化成边界严谨的代码的高频题尤其在C岗位的笔试里出现率不低。我复盘了一下这道题之所以受欢迎是因为它完美地考验了三件事能否把一圈一圈的直觉转化成四个for循环的精确描述能否在状态更新时比如上边界下移、右边界左移意识到边界条件的改变能否跳出我是按数字顺序填的这个线性思维切换到我是按位置顺序填的这个空间思维大部分卡住的人问题都出在第三点上。他们试图用一个变量追踪当前数字然后用某种公式计算数字对应的坐标。这当然可以比如可以用层次和偏移量硬算但代码复杂度远高于直接用方向走位。我见过有人为了这个题写了三十多行数学公式跑起来还容易错。其实最简单的做法是每一轮循环固定填一条边填完就把对应的边界往里缩一格。1.3 这道题到底在训练什么底层能力往深了说环形矩阵是状态机思维的一个入门模型。每一圈的填充可以看作状态依次经历向右、向下、向左、向上四个状态每个状态结束后边界条件发生变化然后进入下一个状态。这不只是数组题它和游戏里角色走格子、机器人在棋盘上清扫路径、图像处理里的区域扫描本质上是同一类问题。我记得有个做嵌入式图形界面的朋友说过他在做LCD屏幕的边框绘制时就遇到了完全类似的逻辑需要把一个区域按螺旋顺序填充颜色用于测试屏幕坏点。你看课堂里看起来没什么用的题到了真实场景里就成了工具。所以我的建议是不要背代码要把这道题当作训练边界管理的思维体操。你把这个能力练扎实了再看其他二维数组相关的问题比如岛屿数量、迷宫寻路、矩阵旋转都会顺畅很多。2. 两种核心思路边界收缩法和方向向量法环形矩阵的解法网上一搜一大把但归纳起来其实就两大流派边界收缩法和方向向量法。我个人建议两个都掌握因为它们各有不可替代的场景。2.1 边界收缩法像查户口一样一圈一圈往里走边界收缩法的核心思想是维护四个变量top、bottom、left、right分别表示当前还没有被填写的区域的上、下、左、右边界。一开始top 0; bottom n - 1; left 0; right n - 1;接着在一个大循环里做四件事从左到右填充top这一行填完top加1说明上边界已经被填死了从上到下填充right这一列填完right减1从右到左填充bottom这一行填完bottom减1从下到上填充left这一列填完left加1每次循环就是一圈。填完一圈后top、bottom、left、right往中间缩了一圈如果top仍然小于等于bottom且left仍然小于等于right说明中间还有没填的区域继续下一圈。我用查户口来类比你手里有一张纸表示还没走到的区域。你走到这块区域的上边从左到右挨个敲门登记登记完把这行撕掉然后走到右边从上到下登记登记完把这一列撕掉再走到下边从右到左登记撕掉最后走到左边从下到上登记撕掉。一圈撕完手里的纸变小了继续重复。直到整张纸都被撕光。这个方法的优点是逻辑非常直观几乎不需要额外的状态判断。缺点是代码里要小心处理只剩一行或只剩一列的情况。比如填充完上边和右边之后如果此时top已经大于bottom说明已经没有剩余的行了第三和第四步再做就会重复赋值导致数据被覆盖。2.2 方向向量法让矩阵自己学会拐弯方向向量法的思路完全不一样。它不维护四个边界而是维护一个当前位置和一个当前方向每一步往前走一格如果发现前面走不了就右转90度继续走。方向用二维数组表示int dirs[4][2] {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};这四组数分别表示向右列1、向下行1、向左列-1、向上行-1。算法的主循环就一句话从1数到n乘n每个数字填到当前格子然后试着往前走一步。如果下一步越界了或者下一步的位置已经有值就转弯。转弯就是方向数组的下标加1再对4取模dir (dir 1) % 4;这个方法天然解决了一个问题不需要考虑只剩一行或只剩一列的特殊情况。因为判断条件永远是下一步能不能走不能走就转即使最后只剩一个格子也能正确填完。我还记得第一次理解这个算法时的感觉原来代码可以像一个小机器人一样自己探索着前进碰到墙就转弯。这个思路比边界收缩法更接近真实世界的导航逻辑也更容易扩展到地图探索、贪吃蛇、迷宫求解等问题上。2.3 两种方案怎么选我用一张表把区别列出来方便你根据实际情况选对比维度边界收缩法方向向量法代码量略长较短边界维护4个变量直观1个方向变量抽象特殊边界需要if防止重复自动处理性能O(n^2)O(n^2)可扩展性调整边界即可调整方向数组即可理解门槛低中如果你是初学者我建议先掌握边界收缩法。它的每一步都能和图形对应上debug的时候可以很清楚地知道现在填到哪一行。等写熟练了再学方向向量法。方向向量法在面试时写起来更简洁也更好口头解释。说实话我自己刷题的时候通常用方向向量法因为它不容易出现边界条件的逻辑漏洞。但是如果题干要求从外到内一圈一圈这种描述我也会立刻切换回边界收缩法的视角来思考。两种方法都是工具关键是在需要的时候能拿出来正确的那个。3. 完整源码边界收缩法的逐行实现现在进入正题给出可以直接抄走的源码。我下面这份代码是以边界收缩法为主体的完整实现包含生成和打印两个函数拿到就能编译运行。3.1 可直接编译运行的完整代码#include iostream #include vector #include iomanip using namespace std; // 打印二维矩阵方便观察效果 void printMatrix(const vectorvectorint matrix) { for (const auto row : matrix) { for (int val : row) { cout setw(3) val; } cout endl; } } // 生成n阶顺时针环形矩阵 vectorvectorint generateSpiralMatrix(int n) { // 初始化一个n*n的二维vector所有元素默认值为0 vectorvectorint matrix(n, vectorint(n, 0)); int top 0, bottom n - 1; int left 0, right n - 1; int num 1; while (top bottom left right) { // 1. 从左到右填充当前上边这一行 for (int j left; j right; j) { matrix[top][j] num; } top; // 2. 从上到下填充当前右边这一列 for (int i top; i bottom; i) { matrix[i][right] num; } --right; // 3. 从右到左填充当前下边这一行 // 注意如果上面的 --top 导致 top bottom说明没有剩余行了 if (top bottom) { for (int j right; j left; --j) { matrix[bottom][j] num; } --bottom; } // 4. 从下到上填充当前左边这一列 // 同理如果 --right 导致 left right说明没有剩余列了 if (left right) { for (int i bottom; i top; --i) { matrix[i][left] num; } left; } } return matrix; } int main() { int n; cout 请输入矩阵阶数 n; cin n; vectorvectorint result generateSpiralMatrix(n); printMatrix(result); return 0; }这份代码在Visual Studio 2022和g 11.3下都编译运行过。输入5输出是这样的1 2 3 4 5 16 17 18 19 6 15 24 25 20 7 14 23 22 21 8 13 12 11 10 9第3行第3列的25正好是中心数字说明奇数阶矩阵的中心处理正确每一行、每一列的数字也都满足螺旋递增的规律。3.2 核心代码段的逐行解读我挑几个容易被忽略的细节重点说。先看vector的初始化vectorvectorint matrix(n, vectorint(n, 0));这一行创建了n行每行是一个长度为n、默认值为0的vector。把默认值设成0后面在方向向量法中可以作为是否已填充的判断依据在这段边界收缩法里其实用不到0这个值但保留它没坏处。需要注意的是vector的初始化是先外层后内层如果你写成vectorvectorint matrix(n, 0)那是在创建n个整数0而不是n行数组编译直接报错。这个低级错误我见过不少新手犯。再看num这个表达式。这里用的是后置自增它返回当前值然后才加1。所以matrix[top][j] num;等价于先赋值再把num加1。如果你用前置自增num那第一个填进去的数字就会变成2而不是1整张表全都从2开始全盘错位。这个细节面试时也经常作为追问点出现。然后是边界更新。每次填完一条边都要动一个边界变量top、--right、--bottom、left。注意动的方式上边界是往下走所以右边界是往左走所以--下边界是往上走所以--左边界是往右走所以。这四个方向如果搞错一个整圈就会乱套。我调试的时候经常把--right写成right结果数字全都往右跑越界后程序直接崩溃。第三步和第四步前面的if判断是整个算法最精华的地方。没有这两个if当n为奇数时最后一圈会出问题。n等于5的时候最后一圈只剩中心一个格子此时top等于bottom等于left等于right都等于2第一步和第二步已经把中心填完了接着第三步的从右到左会再给matrix[bottom][j]赋值把中心的值从25改成什么东西不对仔细看第一步和第二步填完之后top变成了3此时top bottom所以第三步不会执行。但如果去掉if第三步的for循环条件是for (int j right; j left; --j)此时right2left2会进去执行一次再用num把中心覆盖掉最后中心变成26而且num超出了n的平方。这是经典的错误来源。3.3 如何快速验证代码是否正确写完代码我建议不要直接提交或者继续写下一个题先做三个验证n1输出只有1这个用例专门验证最小规模的情况。很多算法在n1时会出现特殊问题因为while循环只会执行一次四步里面后面两步因为边界条件直接跳过。n2输出1 2 4 3这验证了最基本的螺旋顺序。n2时第二圈只剩两格重点看第三步是否能正确执行。n5验证奇数阶的中心处理。中心数字必须是n的平方25。我用这几个用例跑一遍基本能排除90%的边界问题。如果你的代码在n5时中心不是25那问题几乎一定出在第三或第四步的if判断上。4. 最容易翻车的四个边界细节这个章节我单独拎出来因为环形矩阵的坑几乎全集中在边界细节上。我把它们按出现频率从高到低列出来。4.1 奇数阶矩阵的中心点重复赋值这是最经典的坑。n为奇数时比如5最后一圈只剩一个中心格子。问题在于如果代码不看if条件第三步或第四步会在这个中心格子上再赋值一次。复盘一遍n5的流程第一圈填完top从0变1bottom从4变3left从0变1right从4变3此时1 3 1 3继续。第二圈填完top变2bottom变2left变2right变2此时2 2 2 2继续。第三圈开始时top2bottom2left2right2。第一步从左到右把25填到matrix[2][2]然后top变3。此时进入while条件判断top(3) bottom(2)为假循环结束。所以如果写得正确第三步和第四步根本不会执行。但如果你在第一步后面不加其他判断第二步执行时i从top(3)开始此时top3bottom2循环不会执行然后--right把right从2变成1。接着第三步的if条件是if (top bottom)也就是3 2为假跳过。第四步也一样跳过。这也是能工作的。真正出问题的情况是有人在每个方向的for循环里忘了基于当前边界做正确缩进或者把边界更新的位置写乱了。比如有人会在第一步循环里顺手修改right导致后续判断全乱。所以我的核心建议是把边界更新固定写在每个for循环结束后并且四个更新方向严格对应。不要试图在for的循环条件里顺手做更新那样虽然能省一行代码但可读性和排错性都会变差。4.2 越界访问数组下标别踩出边界越界访问在C里是个大问题因为它不像Java或者Python那样会立刻抛异常而是可能看起来还能继续跑但数据已经写到了未知内存区域。表现可能是输出乱码也可能是程序崩溃。更可怕的是有时候代码能正常跑完只是在后续内存释放时崩溃。我建议在调试阶段开启编译器的边界检查选项。g可以用-D_GLIBCXX_ASSERTIONS或者使用AddressSanitizerg -fsanitizeaddress -g spiral.cpp -o spiral这样如果代码里有越界访问运行时马上会报错定位到具体行。Visual Studio里面Debug模式下默认会检查vector访问越界。回到代码本身越界的根源通常是边界更新有误。比如第二步for (int i top; i bottom; i)如果top因为第一步已经变成了1bottom还是n-1那么i从1跑到n-1是安全的。但如果第一步没有top那么top还是0i从0跑到底右边这一列最上面的格子matrix[0][right]会被第二次赋值而且赋值完后right也减了1整个数据就全乱了。我把一个自查口诀分享给你每填完一条边就看一下对应的边界变量是否往矩阵中心缩了一格如果没有缩后面所有循环都会基于错误的边界继续算。4.3 方向向量法的转向优先级问题方向向量法虽然实现短但它也有一个隐蔽的坑转向的判断和前进的时机。正确逻辑是先填当前格子然后计算下一步位置判断下一步是否合法如果不合法就转向再重新计算一次下一步位置最后把当前位置更新为下一步。伪代码matrix[row][col] num; int nextRow row dirs[dir][0]; int nextCol col dirs[dir][1]; if (下一步不合法) { dir (dir 1) % 4; nextRow row dirs[dir][0]; nextCol col dirs[dir][1]; } row nextRow; col nextCol;有人会写成先往前挪发现不合法再退回来转向。这样不是不行但代码多了一步回退容易出错。还有人在转向之后不重新计算nextRow和nextCol而是继续用旧的方向移动那就会穿透边界直接越界。我调试方向向量法时最喜欢在每一步打印当前坐标和方向值cout num num dir dir row row col col endl;看几个数字就能发现问题在哪一步非常高效。4.4 防御性赋值矩阵初始值的选择方向向量法依赖matrix[nextRow][nextCol] ! 0来判断当前位置是否已经填过。这个设计的隐含前提是有效数字从1开始0代表未填。所以初始化矩阵时必须全部置0。这里有个隐藏问题如果题目允许数字从0开始填那么0就不再是未填的标志了这时候还用它判断就会出错。如果在面试现场遇到这种变形你需要用一个额外的bool二维数组来标记是否已填或者改用边界收缩法。我看过有人用vectorvectorint matrix(n, vectorint(n, -1))然后用-1作为未填标志也可以。关键是把这个约定写在注释里提醒自己这里-1是特殊值不是真实数据。防御性编程的原则是把不变量写在代码注释里让维护的人一眼看到。环形矩阵的不变量有三个每个格子恰好被填一次填完的方向序列依次是向右、向下、向左、向上边界变量永远指向下一个待填入的位置。你写完代码后可以逐条核对这三个不变量是否成立能快速定位逻辑漏洞。5. 从会写到会变环形矩阵的几种常见变形学会了基础版本我们来看看这个题怎么变形。面试官最喜欢干的事就是把基础题稍微改一下看你是否真的理解而不仅仅是背代码。5.1 逆时针环形矩阵逆时针环形矩阵是最简单的变形只需要调整方向顺序。方向向量法改成int dirs[4][2] {{1, 0}, {0, 1}, {-1, 0}, {0, -1}};也就是第一步往下走然后向右然后向上然后向左。这样从左上角开始先沿第一列向下填再沿最后一行向右填……整体就是逆时针螺旋。边界收缩法也可以做只要调整内层四个for循环的顺序和方向从上到下填左边这一列从左到右填下边这一行从下到上填右边这一列从右到左填上边这一行注意如果你沿用上面的代码只是简单把前面的{{0, 1}, {1, 0}, {0, -1}, {-1, 0}}改为{{1, 0}, {0, -1}, {-1, 0}, {0, 1}}先下再左再上再右那就成了另一种图案从左上角开始先下到左下角然后向右到右下角……这样整体是逆时针路径方向和初始位置组合起来就会形成不同的起点路径。关于变形的方向顺序我建议每次改完都先跑一遍n4用输出对比预期图形。这里多说一句方向数组的排列顺序本质上定义了机器人的转弯规则。顺时针的四个方向是右下左上逆时针是下右上左。你只要改这一处所有逻辑自动适配。5.2 矩形螺旋矩阵M×N题目升级给一个m行n列的矩阵按螺旋顺序填入1到m乘n。这个变体在力扣和面试中出现频率极高。边界收缩法只需要把n改成两个维度vectorvectorint generateSpiralMatrixMN(int m, int n) { vectorvectorint matrix(m, vectorint(n, 0)); int top 0, bottom m - 1; int left 0, right n - 1; int num 1; while (top bottom left right) { for (int j left; j right; j) { matrix[top][j] num; } top; for (int i top; i bottom; i) { matrix[i][right] num; } --right; if (top bottom) { for (int j right; j left; --j) { matrix[bottom][j] num; } --bottom; } if (left right) { for (int i bottom; i top; --i) { matrix[i][left] num; } left; } } return matrix; }这里的坑在于矩形矩阵可能最后剩的是一行或者一列而不是一个点。比如5行2列的矩阵螺旋到中间时会只剩一列此时第一步填不了left right直接进入第二步上到下填这一列。如果你按照标准4步走第一步的for循环j left; j right因为left0、right1还是会执行这会导致第一行被重复赋值。不对仔细看矩形矩阵在while循环条件成立时第一步一定可以填因为left right。真正需要防的仍然是第三和第四步——当只剩一列但还多行时第二步填完后top会增加此时可能top bottom第三步的if会拦住当只剩一行且多列时第一步填完后right减少第四步的if会拦住。我用5行2列跑了一遍1 2 3 4 5 6 7 8 9 10不对这好像是直接逐行填了不是螺旋。咱们重新想5行2列的螺旋填充预期1 2 10 3 9 4 8 5 7 6我的代码跑出来确实应该是这样。while循环第一圈第一步填第0行第0列和第1列得到1,2top变1。第二步填右边列i从1到4得第1行第1列3、第2行第1列4、第3行第1列5、第4行第1列6right变0。第三步top(1) bottom(4)为真填down这一行即第4行j从0到0得7bottom变3。第四步left(0) right(0)为真从i3到1往上填左边列得8、9、10left变1。循环条件top(1) bottom(3)为真left(1) right(0)为假循环退出。结果对吗填满10个格子1,2,3,4,5,6,7,8,9,10坐标分别是(0,0),(0,1),(1,1),(2,1),(3,1),(4,1),(4,0),(3,0),(2,0),(1,0)。打印出来就是1 2 10 3 9 4 8 5 7 6完美。这个例子说明矩形矩阵的while条件top bottom left right在只剩一行或只剩一列时依然能正确退出不会死循环。你可以用3行5列再测一次预期是1 2 3 4 5 12 13 14 15 6 11 10 9 8 7这也是对的。第三个和第四个if在这里彻底拦住了重复赋值。5.3 从中心向外螺旋填充这个变形就比较有意思了不是从外圈开始而是从正中心开始顺时针一圈圈往外填。要求n为奇数否则没有严格的正中心。实现思路其实可以把方向向量法反向使用数字1放在中心然后按照某种顺序向外走。但反过来走时是否越界不能用矩阵边界判断因为初始位置在中间越界远着呢。这时候可以用距离判断来确定什么时候该转向。我自己的做法是观察从中心出发走螺旋的路径可以发现它是按照步长1、1、2、2、3、3、4、4……这样的节奏走的也就是每次移动的步长每两个方向增加1。方向顺序是向上、向左、向下、向右——注意是从中心出发的上左下右因为从中心开始走第一格算是向上。写出来的核心循环大概长这样vectorvectorint generateOutwardSpiral(int n, int centerRow, int centerCol) { vectorvectorint matrix(n, vectorint(n, 0)); centerRow n / 2; centerCol n / 2; int row centerRow, col centerCol; int dirs[4][2] {{-1, 0}, {0, -1}, {1, 0}, {0, 1}}; int dir 0; int step 1; int num 1; matrix[row][col] num; while (num n * n) { for (int s 0; s 2; s) { for (int i 0; i step; i) { row dirs[dir][0]; col dirs[dir][1]; matrix[row][col] num; } dir (dir 1) % 4; } step; } return matrix; }这个算法的核心是每个步长走两次步长1走两个方向共2格步长2走两个方向共4格以此类推。这段代码我只测试了n为奇数的标准情况如果你要用在更大的矩阵或者偶数阶矩形上还需要额外处理边界。从中心向外螺旋这种题在工程里也有应用比如图像处理中从热点区域向外扩散扫描像素。5.4 环形矩阵的打印模式有些题目不是让你生成矩阵而是给一个已经填好数据的矩阵让你按螺旋顺序打印出来。比如给定1 2 3 4 5 6 7 8 9要输出1 2 3 6 9 8 7 4 5。这和填充正好是反方向的操作。填充是写入打印是读取。但边界管理逻辑几乎一样还是四个边界变量四步走只是每步从赋值变成输出。这里有个小坑打印时要注意不要输出多余的空格以及最后一个元素后面不要输出分隔符。我一般先把所有螺旋顺序的数字push到一个vector里再统一用空格连起来输出。这样格式好控制也便于后续处理。示例代码片段void printSpiralOrder(const vectorvectorint matrix) { int m matrix.size(); if (m 0) return; int n matrix[0].size(); int top 0, bottom m - 1, left 0, right n - 1; vectorint res; while (top bottom left right) { for (int j left; j right; j) res.push_back(matrix[top][j]); top; for (int i top; i bottom; i) res.push_back(matrix[i][right]); --right; if (top bottom) { for (int j right; j left; --j) res.push_back(matrix[bottom][j]); --bottom; } if (left right) { for (int i bottom; i top; --i) res.push_back(matrix[i][left]); left; } } for (int i 0; i res.size(); i) { if (i) cout ; cout res[i]; } cout endl; }对比一下就会发现它和generateSpiralMatrix的结构一模一样只是内层操作从写换成读。所以只要你真正理解了填充的逻辑打印的题就是送分题。6. 刷题和面试时的一些经验提醒说到最后我想分享一些代码本身之外的经验这些都是我在实际做题、面试和给别人讲题过程中沉淀下来的。6.1 复杂度分析别忽略环形矩阵生成的时间复杂度是O(n^2)空间复杂度是O(n^2)因为要返回整个矩阵。如果你考虑辅助空间边界收缩法只用了四个int变量可以说是O(1)辅助空间方向向量法除了矩阵外只用了方向数组和几个int也是O(1)辅助空间。面试时如果被问到能不能优化空间你可以回答如果只是打印螺旋顺序可以在生成过程中直接输出不需要存储整个矩阵但如果题目要求返回矩阵那么O(n^2)空间是不可避免的因为答案本身就是O(n^2)的数据。有人可能会问能不能不用vector用数组完全可以用int matrix[n][n]在C中不是标准做法变长数组不是标准C特性所以我建议用vector这也是现代C的推荐方式。如果你在嵌入式环境里需要固定大小可以用std::array或者动态内存分配。实际工程中vector的开销可以忽略不计因为它本质上是三个指针加堆内存。6.2 面试官喜欢追问的几个点我在模拟面试时经常把这几个点作为追问第一如果n非常大比如10000代码能不能改成原地生成而不额外占用内存实际上不行因为你要返回矩阵答案本身就是O(n^2)的。但你可以在函数内部复用输入参数比如题目给的矩阵本来就是n×n的空矩阵那么直接在里面填数就是原地操作。第二为什么方向向量法中要用matrix[nextRow][nextCol] ! 0来判断是否已填如果用 0来判断有什么隐患隐患是你必须保证所有未填位置都是0而这需要初始化时全部置0。如果题目从0开始填数这个条件就失效了。第三边界收缩法里第三步和第四步的if条件可以去掉吗我说可以但要去掉就需要在while循环条件里做额外的判断比如在第三步前判断是否top bottom再决定是否退出。但直接写if更清晰面试官通常认可这个解释。第四如果矩阵不是正方形比如3行4列结果应该长什么样这其实就是前面讲的M×N变体。你要能迅速指出while条件不变第三步和第四步的if仍然需要。6.3 我给正在刷题的人一个具体的练习路径如果你之前完全没接触过环形矩阵我的建议是第一步先把边界收缩法的代码抄一遍手推开n3和n4的每一步确认每个变量的变化。这一步的目标是建立边界收缩的直觉。手推就是自己在纸上把top、bottom、left、right的值写下来跟着代码一步一步划掉已填的格子。这个动作看起来很笨但比盯着代码看一小时都管用。第二步不看代码自己从零写一遍边界收缩法。写完用n1、2、3、4验证。这一步的目标是检测你是否真的理解了边界更新的顺序。第三步实现方向向量法跑同样的测试用例。这一步是训练状态转换思维。第四步把自己做过的变形题逆时针、矩形、从中心向外全部用两种方法实现一遍。按照这个路径练下来环形矩阵这个类别的题你基本能做到见题就写。不用背任何代码因为每一步的逻辑你已经内化了。我在讲给自己的朋友听的时候经常说一句话环形矩阵是我见过的最典型的脑子会了手不会的题目。解决它的关键不是聪明而是把边界变化在草稿纸上画清楚。你只要愿意画一遍图这道题难度降一半。另外提醒一下环境问题写C代码时如果用Visual Studio默认会启用SDK检查vector越界时会弹对话框如果在Linux上用g编译建议加上-Wall -Wextra把警告全显出来很多边界错误其实编译器能提前给出warning。我自己习惯用的调试命令是g -stdc17 -Wall -Wextra -g spiral.cpp -o spiral顺手把编译标准定到C17方向数组、vector、auto这些特性都能直接用没什么兼容性问题。最后再分享一个小技巧如果你在家里练习题目做错了想复盘别急着看题解。先把错误的版本另存为一个文件猜一猜错误会发生在哪个用例下——这个预告错误的过程比直接改对更能加深印象。反正我自己是靠这个方法把边界类题目彻底练熟的。
网站建设高端定制企业官网