C++实现螺旋矩阵II的算法解析与优化
1. 螺旋矩阵II问题解析
最近在力扣刷题时遇到了经典的螺旋矩阵II问题(编号59),这道题看似简单但实现起来有不少细节需要注意。题目要求给定一个正整数n,生成一个包含1到n²所有元素的n×n正方形矩阵,且这些元素按照顺时针螺旋顺序排列。
作为C++选手,我花了些时间研究这个问题,发现核心在于控制好边界条件和遍历方向。下面分享我的解题思路和实现过程,希望能帮助同样在刷题的朋友们少走弯路。
2. 解题思路分析
2.1 问题分解
螺旋矩阵的生成可以分解为四个方向的循环填充:
- 从左到右填充上行
- 从上到下填充右列
- 从右到左填充下行
- 从下到上填充左列
每个循环完成后,对应的边界会向内收缩一层。这个过程需要持续到所有元素填充完毕。
2.2 边界条件处理
关键是要处理好四个边界:
- 左边界(left)
- 右边界(right)
- 上边界(top)
- 下边界(bottom)
每次完成一个方向的填充后,对应的边界需要调整。例如完成从左到右的填充后,上边界top需要加1。
3. C++实现详解
3.1 初始化矩阵
首先需要初始化一个n×n的二维vector:
vector<vector<int>> generateMatrix(int n) { vector<vector<int>> matrix(n, vector<int>(n)); // 后续代码... }3.2 主循环实现
使用while循环控制整体流程,直到填充完所有数字:
int num = 1; int left = 0, right = n - 1; int top = 0, bottom = n - 1; while (left <= right && top <= bottom) { // 四个方向的填充代码... }3.3 四个方向填充细节
3.3.1 从左到右填充上行
for (int i = left; i <= right; i++) { matrix[top][i] = num++; } top++;3.3.2 从上到下填充右列
for (int i = top; i <= bottom; i++) { matrix[i][right] = num++; } right--;3.3.3 从右到左填充下行
for (int i = right; i >= left; i--) { matrix[bottom][i] = num++; } bottom--;3.3.4 从下到上填充左列
for (int i = bottom; i >= top; i--) { matrix[i][left] = num++; } left++;4. 完整代码实现
将上述部分组合起来,完整的解决方案如下:
vector<vector<int>> generateMatrix(int n) { vector<vector<int>> matrix(n, vector<int>(n)); int num = 1; int left = 0, right = n - 1; int top = 0, bottom = n - 1; while (left <= right && top <= bottom) { // 从左到右 for (int i = left; i <= right; i++) { matrix[top][i] = num++; } top++; // 从上到下 for (int i = top; i <= bottom; i++) { matrix[i][right] = num++; } right--; // 从右到左 for (int i = right; i >= left; i--) { matrix[bottom][i] = num++; } bottom--; // 从下到上 for (int i = bottom; i >= top; i--) { matrix[i][left] = num++; } left++; } return matrix; }5. 复杂度分析
5.1 时间复杂度
由于我们需要填充n²个元素,每个元素只被访问一次,因此时间复杂度为O(n²)。
5.2 空间复杂度
除了返回的矩阵外,我们只使用了常数个额外变量,因此空间复杂度为O(1)(不考虑返回矩阵的空间)。
6. 边界情况处理
6.1 n=1的情况
当n=1时,矩阵只有一个元素[[1]],我们的代码也能正确处理这种情况。
6.2 奇数和偶数n
无论n是奇数还是偶数,代码都能正确处理。对于奇数n,中心点会在最后被填充;对于偶数n,所有层都能完整填充。
7. 调试技巧
7.1 打印中间结果
在开发过程中,可以在每个方向填充后打印当前矩阵状态,方便调试:
void printMatrix(const vector<vector<int>>& matrix) { for (const auto& row : matrix) { for (int num : row) { cout << num << "\t"; } cout << endl; } cout << "-----------------" << endl; }7.2 边界值测试
建议测试以下情况:
- n=1
- n=2
- n=3
- n=4 确保各种边界情况都能正确处理。
8. 常见错误与修正
8.1 边界条件错误
常见错误是边界条件处理不当,导致重复填充或漏填。例如:
- 忘记更新边界(top++, right--等)
- 循环条件错误(使用<而不是<=)
8.2 索引越界
在从右到左和从下到上填充时,要特别注意索引不要越界。确保:
- 右边界right不小于左边界left
- 下边界bottom不小于上边界top
9. 优化思路
9.1 减少循环次数
可以观察到当left == right时,只需要填充垂直方向;当top == bottom时,只需要填充水平方向。可以添加特殊处理:
if (left == right) { for (int i = top; i <= bottom; i++) { matrix[i][left] = num++; } break; } if (top == bottom) { for (int i = left; i <= right; i++) { matrix[top][i] = num++; } break; }9.2 预分配内存
虽然vector会自动管理内存,但对于大n值,预先分配好内存可能有一定性能提升:
matrix.reserve(n); for (auto& row : matrix) { row.reserve(n); }10. 类似题目推荐
掌握了螺旋矩阵II后,可以尝试以下类似题目:
- 螺旋矩阵I(编号54):给定矩阵按螺旋顺序读取
- 旋转图像(编号48):顺时针旋转图像90度
- 对角线遍历(编号498):按对角线顺序遍历矩阵
11. 个人心得
在实际编码过程中,我发现画出矩阵的示意图对理解很有帮助。可以用纸笔画出n=3、n=4的情况,标出填充顺序和边界变化。这样能更直观地理解算法流程。
另一个技巧是使用一致的变量命名。我选择left/right/top/bottom这种直观的命名,而不是更短的l/r/t/b,虽然代码稍长但可读性更好。
最后,边界条件的处理是这类问题的关键。建议先处理一般情况,再仔细考虑各种边界情况,确保代码的健壮性。