资讯动态

LeetCode 1572 矩阵对角线求和:从 O(n²) 到 O(n) 的优化与边界处理

发布时间:2026/10/13 12:54:11 来源:尧图企业网站定制
如果你正在刷 LeetCode大概率已经在题库里见过这道“1572. 矩阵对角线求和”。题干短得不能再短给定一个 n x n 的方阵返回主对角线和副对角线上所有元素的和重叠元素只计一次。我印象里这道题在“简单”档里都属于最省事的那类很多人五分钟内就能通过然后顺手划走。但说真的这道题背后能挖的东西比表面看起来多不少矩阵索引的几何直觉、时间复杂度从 O(n²) 到 O(n) 的优化意识、奇偶矩阵边界条件的分情况讨论每一样都是后续做矩阵类题目绕不开的基本功。这篇文章我会从题目本身的隐藏信息讲起把两种主流解法的推导过程完整走一遍再针对中心元素重复这个经典坑做详细拆解最后聊一聊面试里常见的变体以及刷题时怎么把一道简单题的价值真正榨干。刚开始接触矩阵题目的新手、准备面试想夯实基础的朋友甚至刷了一段时间但总在简单题上“会做不会讲”的人都可以从里面找到自己需要的部分。1. 题目到底在考什么读懂题干比AC更重要1.1 题干里的三个隐藏信息方阵、对角线公式、n1很多人AC了这道题但并没有意识到题干里其实藏着三个关键点。第一个是方阵也就是矩阵的行数和列数相等。只有方阵才谈得上“主对角线”和“副对角线”因为两条对角线都是从矩阵的一头延伸到另一头要求横纵坐标覆盖整个尺寸。LeetCode的测试用例保证输入是方阵所以做题时不需要处理矩形但自己理解题目时必须知道这个前提。第二个隐藏信息是两条对角线的坐标条件。主对角线上的元素满足行号 列号坐标序列是(0,0), (1,1), ..., (n-1,n-1)副对角线上的元素满足行号 列号 n - 1坐标序列是(0,n-1), (1,n-2), ..., (n-1,0)。如果能用i表示当前行号那么第i行上主对角线元素就是mat[i][i]副对角线元素就是mat[i][n-1-i]。这两个公式是整个题目的灵魂比任何花哨的解法都重要。第三个隐藏信息藏在n1这种最小输入里。当矩阵只有一个元素时主对角线和副对角线指向同一个坐标这个元素理论上应该被计算两次但题目要求“重叠只计一次”所以结果就是那一个值本身。我在评论区见过不少人挂在n1上原因就是默认两条对角线必然是两个不同元素忘记了极端情况下它们会完全重合。这种对边界条件的敏感度恰恰是简单题最想考察的东西。1.2 暴力解法为什么能过但我不推荐你止步于此最直观的写法是双重循环遍历每个元素判断它是不是落在某条对角线上是就累加。这种解法的代码几乎不可能写错而且LeetCode对这道题的数据范围很宽松O(n²) 也能轻松通过。但我一直认为对一道题的理解不能停在“能过”这个层面。暴力解法的本质是把所有格子都访问一遍然后逐个判断。问题是矩阵里大量元素根本不在对角线上判断它们只是在空转。当n很小时感受不到差别但一旦矩阵规模变大比如n1000双重循环就需要做一百万次判断而单层循环只需要遍历一千次。这个差距就是 O(n²) 和 O(n) 的距离也是面试官最常追问的“能不能优化”的点。更重要的是思维习惯的问题。如果每次做矩阵题都默认“遍历所有格子再筛”那么遇到需要按行、按列、按特定几何关系取元素的题目时就很难快速建立起坐标和位置之间的对应关系。1572恰好是培养这种敏感度的入门题把最优解想明白后面做中等难度的矩阵题会顺手很多。2. 从双重循环到单层循环两种解法的完整推导2.1 兜底写法双重循环加坐标条件判断先说第一种解法它的思路很直白枚举所有坐标用i j判断主对角线用i j n - 1判断副对角线满足任一条件就累加。Python代码大概长这样def diagonalSum(self, mat): n len(mat) total 0 for i in range(n): for j in range(n): if i j or i j n - 1: total mat[i][j] return total这段代码在功能上是完全正确的心思也基本花在了“判断”而不是“取数”上。我认可它作为兜底方案的价值笔试时间紧张、题目理解还不透彻的时候先写一个绝对正确的版本保底再逐步优化是非常理性的策略。很多竞赛选手也是这么干的第一遍提交用最稳的写法AC之后再去想更优方案。但我不建议把这段代码当作最终答案。因为它在每次循环里都做了两次比较运算而且遍历了大量无关元素。如果面试官接着问“你的时间复杂度是多少能不能更好”而你只能回答O(n²)这一轮就很被动了。所以它更适合作为思考的起点而不是终点。2.2 最优写法单层循环配合索引公式把复杂度降到 O(n)再仔细观察一下两条对角线的分布规律你会发现一个关键事实对于任意一行i主对角线元素是mat[i][i]副对角线元素是mat[i][n-1-i]。也就是说每一行恰好有两个元素需要累加我们根本不需要遍历整行。于是代码可以优化成def diagonalSum(self, mat): n len(mat) total 0 for i in range(n): total mat[i][i] total mat[i][n - 1 - i] return total这个版本的时间复杂度是 O(n)空间复杂度是 O(1)。它只遍历了n行而不是n*n个格子。为什么能做这种优化因为两条对角线在第i行的元素坐标是唯一确定的行号一旦确定列号就跟着确定不需要再做任何判断。但这里立刻冒出一个问题当n为奇数时中间那一行的两个坐标mat[i][i]和mat[i][n-1-i]会指向同一个元素比如n3时i1这一行里mat[1][1]和mat[1][1]是同一个位置。上面的代码会把中心元素重复累加一次导致结果偏大。这个问题的完整处理我在下一章专门展开。2.3 为什么“先加后减”比“边加边判断”更干净针对中心元素重复的问题常见的处理有两种一种是在循环里先判断i ! n-1-i再决定要不要加上副对角线元素另一种是不管三七二十一全部加进去最后如果发现n是奇数再减掉一次中心元素。我强烈推荐第二种写法def diagonalSum(self, mat): n len(mat) total 0 for i in range(n): total mat[i][i] total mat[i][n - 1 - i] if n % 2 1: total - mat[n // 2][n // 2] return total理由有三个。第一循环体内的逻辑保持极简没有分支判断对CPU流水线更友好第二代码的可读性更好“先统一累加再处理特例”是人类更容易理解的叙述顺序第三中心元素只可能重复一次把特例处理放在循环外天然比在每次迭代里反复检查更高效。这一个细节不会影响题目AC但在代码评审时很能体现你对边界情况的思考习惯。3. 边界处理与中心元素陷阱奇数矩阵为什么特殊3.1 中心重复的数学本质两条对角线相交的条件两条对角线什么时候会相交答案很简单当主对角线坐标(i,i)和副对角线坐标(i,n-1-i)重合时。把两个坐标的行号相等、列号也相等列出来就是i n - 1 - i解这个方程得到i (n - 1) / 2。这个i要成为合法的整数行号n - 1必须是偶数也就是说n本身是奇数。所以判断条件只有一个n % 2 1。一旦确认是奇数矩阵中心坐标就是(n // 2, n // 2)。如果n是偶数主对角线和副对角线各走各的完全没有交集不需要做任何额外处理。这个结论我建议是“理解推导”而不是“死记结论”因为面试时换一种问法比如“如果矩阵不是方阵两条对角线还会相交吗”只背结论的人就会卡住。3.2 手算3×3与4×4两个实例用例子验证公式理论推导说得再多不如手算一个例子来得直观。我拿一个3×3矩阵演示1 2 3 4 5 6 7 8 9主对角线是1 5 9 15副对角线是3 5 7 15两条对角线加起来是30但中心的5被算了两次所以正确答案是30 - 5 25。按我们的代码流程走一遍i0累加1 3 4i1累加5 5 10此时累计14i2累加9 7 16累计30最后n为奇数减去mat[1][1] 5得到25。完全一致。再看一个4×4矩阵1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16主对角线是1 6 11 16 34副对角线是4 7 10 13 34两条线没有交集答案就是68。代码流程i0累加145i1累加6713累计18i2累加111021累计39i3累加161329累计68因为n是偶数不做修正。这两个例子放在一起奇偶差异一目了然。3.3 防御式写法空矩阵、非方阵与整数溢出LeetCode的测试用例中不会出现空矩阵但我写通用函数时习惯加上防御判断。最简单的情况是if not mat or len(mat) ! len(mat[0]): return 0这样即使外部传入矩形或空数组函数也不会报越界错误。这个习惯在刷题时看起来多余但在实际工程中非常有用因为线上数据永远不会像题目假设那样乖巧。另一个容易被忽视的细节是整数溢出。题目里矩阵元素的取值范围可能到10^9当n比较大时累加结果可能超过32位整数的上限。Python的int是任意精度的不会有问题但如果你用C或Java就需要考虑用long或long long来接收结果。面试时主动说出这一点往往能给面试官留下“这个人有工程意识”的印象。4. 面试变体与同类题扩展一道题带动一类题4.1 四个高频变体单条对角线、外侧元素、三角区域、非方阵面试官很喜欢在简单题基础上加一点变化来考察应变能力。我总结过几个高频变体都是围绕1572展开的。第一个变体是“只求主对角线元素和”。这个最简单删掉副对角线累加就好total 0 for i in range(n): total mat[i][i]第二个变体是“求两条对角线之外的元素和”。思路变成“全部元素的和减去对角线元素的和”但要注意中心元素不能减两次。如果你直接用对角线求和公式得到diag_sum那么外侧元素和等于total_sum - diag_sum因为对角线求和时中心元素已经被正确计算了一次直接用这个结果相减是安全的。但如果这里脑子一热把两条对角线分别算完再减就容易多减一次中心元素。第三个变体是“求副对角线及其上方所有元素的和”。这时条件变成i j n - 1需要双重循环但判断条件是区域关系而不是对角线关系考察的是坐标不等式。第四个变体是“输入是m x n矩形求从左上到右下的对角线元素和”。此时主对角线依然好定义但副对角线就不能再用n-1-i了因为行数m和列数n不等每条对角线的长度也不一样。这个变体最能检验你是不是真正理解了坐标公式的推导过程而不是只会套模板。4.2 从1572到热门矩阵题刷题路径与思维迁移最近热词里的“leetcode热门100题”“leetcode 994 腐烂的橘子”“leetcode 073 爱吃香蕉的狒狒”其实是刷题人不同阶段的缩影。1572是矩阵题的地基它教你用坐标关系定位元素而994腐烂的橘子则在这个基础上叠加了图论中广度优先搜索BFS的思想需要用队列模拟腐烂扩散过程每一轮从所有已腐烂的橘子出发向上下左右四个方向感染新鲜橘子。两道题表面的解法完全不同但底层都在做一件事把二维坐标映射成可操作的对象然后按照规则逐层处理。再比如073爱吃香蕉的狒狒它虽然是数组题但核心是二分查找考察的是对查找区间的控制能力。很多人的刷题路线都是先从1572这类简单题建立自信然后过渡到二叉树、BFS、二分查找这些经典专题。刷题顺序本身没有绝对标准但我的建议是简单题要能秒杀最优解中等题要能快速形成正确思路这样考试和面试时才有足够的心理余量去处理新题。周赛里的简单题往往就是1572这种披着各种外衣的基础题把这类题练熟比赛时至少不会在前几题卡壳。5. 刷题实操经验与常见问题简单题也别大意5.1 简单题的正确刷法我的三遍练习法很多人觉得简单题AC了就算过了但我的习惯是“一道题写三遍”。第一遍用最直接的第一反应写不管效率目标是AC第二遍专门想优化方案把O(n²)改成O(n)理解决策依据第三遍找个空白文档或者白板不借助任何提示完整手写一遍再用边界值自测比如n1、n2、n3。三遍下来一道简单题的收获可能比囫囵吞枣做十道题都大。具体到1572第一遍你可能写双重循环判断第二遍改成单层循环加索引公式第三遍要能在白板上流畅写出“先加后减”的完整版本并解释清楚为什么奇数时减中心元素。这个练习流程听起来笨拙但确实是我自己实际用下来效果最好的方式。它能把短期记忆转化为长期理解下次再遇到任何对角线相关题目你不需要临时推导也能直接落笔。5.2 常见报错与排查越界、重复、溢出我把自己刷这道题时遇到过的、以及帮别人看过的典型问题整理成了速查表现象可能原因解决方案数组越界副对角线列号写成n-i使用n-1-i结果比预期大n为奇数时中心元素重复累加判断奇数并减去中心值功能正确但耗时高双重循环遍历所有元素改为单层循环n1时结果错误没有处理两条对角线重合的情况先加后减最终结果为该元素本身其他语言提交溢出int范围不够改用long类型其中最隐蔽的问题是“为什么我只加了两次对角线结果还是多算了一次中心”这个问题的根源是没有意识到当n为奇数时i n-1-i在中间行是成立的两个accumulate语句指向同一个内存地址。表面上是加两个数实际上是同一个数加了两遍。5.3 手写代码的加分细节从能运行到讲得清如果你在面试中手写这道题有几个小细节能明显加分。第一开始写代码前先说明思路比如“两条对角线每行各取一个元素所以只需要遍历行号”这句话能让面试官马上抓住你的思考方向。第二变量命名要有意义n代表边长total代表总和mid代表中心下标不要写一堆i、j、sum糊弄过去。第三写完代码后主动跑一个n3的测试用例口算验证结果这能展示你完整的调试习惯。我个人在面试模拟中观察到一个现象很多候选人能写出正确代码但讲不清楚“为什么奇数要减去中心元素”。其实面试官问这个问题时想听到的不是“题目要求这样”而是“当i n-1-i时中心元素同时属于两条对角线因为它是奇数矩阵唯一的交叉点所以会多计一次需要减去”。这个回答既包含数学推导又包含代码层面的处理逻辑比简单翻题解背答案不知道高到哪里去了。如果要说这道题给我留下最深的一个习惯那就是“看到矩阵先想坐标关系看到简单题先想能不能优化”。1572的代码量不过十行但它把矩阵索引的几何直觉、复杂度优化的思维方式、边界条件的处理模板全部串在了一起。每次我刷完一道简单题都会问自己一句这个解法是不是已经把题目压榨干净了如果你也能带着这个问题去写题那1572对你的价值绝对不止“一道简单题”而已。

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

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

免费获取报价 →
↑