1. 矩阵问题在算法面试中的核心地位
最近在整理LeetCode Hot100的刷题笔记时,发现矩阵类问题出现的频率相当高。这类问题往往看似简单,但要在面试中快速写出bug-free的代码并不容易。今天我们就来系统梳理矩阵类题目的解题套路,掌握这些技巧后,面对旋转、搜索、路径等问题都能游刃有余。
矩阵问题之所以成为面试常客,是因为它能全面考察候选人的以下能力:
- 对二维数据结构的操作熟练度
- 边界条件的处理能力
- 空间复杂度的优化意识
- 递归与迭代的转换技巧
2. 矩阵遍历的四种经典模式
2.1 螺旋遍历(Spiral Order)
这是最常见的矩阵遍历方式,LeetCode第54题就是典型代表。关键在于维护四个边界:top、bottom、left、right,然后按照右→下→左→上的顺序循环。
def spiralOrder(matrix): if not matrix: return [] res = [] top, bottom = 0, len(matrix)-1 left, right = 0, len(matrix[0])-1 while True: # 从左到右 for i in range(left, right+1): res.append(matrix[top][i]) top += 1 if top > bottom: break # 从上到下 for i in range(top, bottom+1): res.append(matrix[i][right]) right -= 1 if left > right: break # 从右到左 for i in range(right, left-1, -1): res.append(matrix[bottom][i]) bottom -= 1 if top > bottom: break # 从下到上 for i in range(bottom, top-1, -1): res.append(matrix[i][left]) left += 1 if left > right: break return res注意:边界变化后要立即检查是否越界,这是最容易出错的地方。我在面试中见过不少候选人忘记检查导致死循环。
2.2 对角线遍历
LeetCode第498题要求按对角线顺序遍历矩阵。观察发现:
- 奇数对角线方向向上,行减列加
- 偶数对角线方向向下,行加列减
def findDiagonalOrder(matrix): if not matrix: return [] m, n = len(matrix), len(matrix[0]) res = [] row = col = 0 for _ in range(m * n): res.append(matrix[row][col]) if (row + col) % 2 == 0: # 向上遍历 if col == n - 1: row += 1 elif row == 0: col += 1 else: row -= 1 col += 1 else: # 向下遍历 if row == m - 1: col += 1 elif col == 0: row += 1 else: row += 1 col -= 1 return res2.3 旋转遍历(Rotate Image)
LeetCode第48题要求原地旋转图像。这类问题的关键是找到旋转前后坐标的映射关系:
- 顺时针90度:matrix[i][j] → matrix[j][n-1-i]
- 逆时针90度:matrix[i][j] → matrix[n-1-j][i]
def rotate(matrix): n = len(matrix) # 先转置 for i in range(n): for j in range(i, n): matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j] # 再水平翻转 for i in range(n): matrix[i] = matrix[i][::-1]2.4 之字形遍历(Zigzag)
这种遍历方式在构建某些特殊矩阵时会用到,核心是控制方向变量:
def zigzag(matrix): if not matrix: return [] m, n = len(matrix), len(matrix[0]) res = [] for i in range(m): if i % 2 == 0: res += matrix[i] else: res += matrix[i][::-1] return res3. 矩阵搜索问题精解
3.1 二分搜索变种
LeetCode第74题(搜索二维矩阵)和第240题(搜索二维矩阵II)是经典变种:
- 第74题可以看作展开的一维数组进行二分
- 第240题需要利用行列有序的特性,从右上角开始搜索
# 第240题解法 def searchMatrix(matrix, target): if not matrix: return False row, col = 0, len(matrix[0])-1 while row < len(matrix) and col >= 0: if matrix[row][col] == target: return True elif matrix[row][col] < target: row += 1 else: col -= 1 return False3.2 岛屿问题系列
岛屿类问题(如200题)通常使用DFS/BFS遍历:
def numIslands(grid): if not grid: return 0 count = 0 for i in range(len(grid)): for j in range(len(grid[0])): if grid[i][j] == '1': self.dfs(grid, i, j) count += 1 return count def dfs(grid, i, j): if i<0 or j<0 or i>=len(grid) or j>=len(grid[0]) or grid[i][j] != '1': return grid[i][j] = '0' # 标记为已访问 self.dfs(grid, i+1, j) self.dfs(grid, i-1, j) self.dfs(grid, i, j+1) self.dfs(grid, i, j-1)实际面试中,面试官可能会要求比较DFS和BFS的实现差异。DFS代码更简洁,但BFS更适合大规模数据。
4. 动态规划在矩阵中的应用
4.1 最小路径和(LeetCode 64)
def minPathSum(grid): if not grid: return 0 m, n = len(grid), len(grid[0]) dp = [[0]*n for _ in range(m)] dp[0][0] = grid[0][0] # 初始化第一列 for i in range(1, m): dp[i][0] = dp[i-1][0] + grid[i][0] # 初始化第一行 for j in range(1, n): dp[0][j] = dp[0][j-1] + grid[0][j] for i in range(1, m): for j in range(1, n): dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j] return dp[-1][-1]4.2 最大正方形(LeetCode 221)
def maximalSquare(matrix): if not matrix: return 0 m, n = len(matrix), len(matrix[0]) dp = [[0]*(n+1) for _ in range(m+1)] max_len = 0 for i in range(1, m+1): for j in range(1, n+1): if matrix[i-1][j-1] == '1': dp[i][j] = min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) + 1 max_len = max(max_len, dp[i][j]) return max_len * max_len5. 矩阵问题优化技巧
5.1 空间复杂度优化
很多矩阵DP问题可以将空间复杂度从O(mn)优化到O(n):
def minPathSum(grid): if not grid: return 0 m, n = len(grid), len(grid[0]) dp = [0]*n dp[0] = grid[0][0] for j in range(1, n): dp[j] = dp[j-1] + grid[0][j] for i in range(1, m): dp[0] += grid[i][0] for j in range(1, n): dp[j] = min(dp[j], dp[j-1]) + grid[i][j] return dp[-1]5.2 方向数组的使用
在处理相邻单元格时,使用方向数组能让代码更简洁:
# 上下左右四个方向 directions = [(-1,0),(1,0),(0,-1),(0,1)] for di, dj in directions: ni, nj = i + di, j + dj if 0 <= ni < m and 0 <= nj < n: # 处理相邻单元格6. 常见错误与调试技巧
索引越界:矩阵问题最容易出现数组越界。建议:
- 先检查空矩阵情况
- 在访问matrix[i][j]前确认i,j的范围
- 使用辅助函数处理边界检查
方向混淆:旋转、遍历方向容易搞混。建议:
- 画图辅助理解
- 用3x3矩阵手动模拟
- 添加详细的注释说明方向
原地修改问题:有些题目要求原地修改矩阵,这时要注意:
- 修改顺序是否会影响后续判断
- 是否需要额外的标记方式
- 能否使用位运算同时存储新旧状态
复杂度过高:矩阵问题容易写出O(mn)空间复杂度的解法。优化思路:
- 观察状态转移是否只需要前一行/列
- 考虑用位图代替二维数组
- 尝试从不同角度进行状态压缩
7. 高频面试题分类训练
7.1 基础操作类
- 旋转图像(48)
- 矩阵置零(73)
- 螺旋矩阵(54)
- 对角线遍历(498)
7.2 搜索类
- 搜索二维矩阵(74)
- 搜索二维矩阵II(240)
- 单词搜索(79)
- 岛屿数量(200)
7.3 动态规划类
- 最小路径和(64)
- 最大正方形(221)
- 不同路径(62)
- 地下城游戏(174)
7.4 其他变种
- 矩阵中的最长递增路径(329)
- 01矩阵(542)
- 矩阵区域和(1314)
- 稀疏矩阵乘法(311)
8. 实战建议
模板化训练:将每种题型总结成固定解题模板,如:
- 螺旋遍历 → 四边界法
- 岛屿问题 → DFS/BFS模板
- 矩阵DP → 初始化首行首列
维度转换思维:有时将矩阵视为图(节点=单元格,边=相邻关系)能获得新思路
复杂度分析:明确告知面试官你的解法时间/空间复杂度,并讨论优化可能
测试用例设计:考虑以下特殊情况:
- 空矩阵
- 1x1矩阵
- 单行/单列矩阵
- 全0/全1矩阵
可视化调试:对于复杂逻辑,可以在纸上画出矩阵和指针移动轨迹辅助理解
最后分享一个我在面试中总结的小技巧:当遇到复杂矩阵问题时,先和面试官确认输入矩阵的特性(是否有序?是否有特殊结构?),这往往能发现题目隐藏的简化条件。