c 面试题之顺时针打印矩阵。输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。例如,输入如下矩阵:
则依次打印出数字1、2、3、4、8、12、16、15、14、13、9、5、6、7、11、10。
第一次看到这个题目的时候,觉得这个题目很简单,完全不需要用到数据结构或算法的知识。后来听到包括Autodesk、EMC在内的多家公司在面试或者笔试里采用过这道题,于是决定自己写一遍试一下。真正写一遍才发现,要完整写出这道题的代码,还真不容易。
解决这道题的难度在于代码中会包含很多个循环,而且还有多个边界条件需要判断。如果在把问题考虑得很清楚之前就开始写代码,不可避免地会越写越混乱。因此解决这个问题的关键,在于先要形成清晰的思路,并把复杂的问题分解成若干个简单的问题。下面分享我分析这个问题的过程。
当遇到一个复杂的问题时,可以用图形辅助思考。由于是以从外圈到内圈的顺序依次打印,可以在矩阵中标注一圈作为分析目标。设矩阵的宽度为columns,高度为rows。选取左上角坐标为(startX, startY),右下角坐标为(endX, endY)的一个圈来分析。
由于endX和endY可以根据startX、startY以及columns、rows来求得,因此只需要引入startX和startY两个变量。而打印第一圈时,左上角的坐标是(0,0),打印第二圈时左上角的坐标是(1,1)。所以,其实左上角的坐标中startX和startY是相等的,表示为(start,start)就可以了。
现在可以想象有一个循环,在每一次循环里,从(start, start)出发按顺时针打印一圈数字。然后start加1,继续打印下一圈。然后来分析这个循环结束的条件。对一个5×5的矩阵而言,最后一圈只有一个数字,对应的坐标为(2, 2)。我们发现5 > 2 * 2。对一个6×6的矩阵而言,最后一圈有四个数字,其左上角的坐标仍然为(2, 2)。6 > 2 * 2依然成立。于是可以得出,让循环继续的条件是columns > start * 2 && rows > start * 2。
void PrintMatrixClockwisely(int** numbers, int columns, int rows)
{
if (numbers == NULL || columns <= 0 || rows <= 0)
return;
int start = 0;
while (columns > start * 2 && rows > start * 2)
{
PrintMatrixInCircle(numbers, columns, rows, start);
++start;
}
}
接下来分析如何在PrintMatrixInCircle中按顺时针打印一圈数字。如同图中标注的那样,可以分四步来打印:第一步是从左到右打印一行(黄色区域),第二步是从上到下打印一列(绿色区域),第三步从右到左打印一行(蓝色区域),最后一步是从下到上打印一列(紫色区域)。
不过值得注意的是,最后一圈有可能退化成只有一行、只有一列,甚至只有一个数字,因此打印这样一圈就不再需要四步。下图是几个退化的例子,打印一圈分别只需要三步、两步甚至只有一步。
仔细分析打印时每一步的前提条件。第一步总是需要的。如果只有一行,就不用第二步了。因此需要第二步的前提条件是终止行号大于起始行号。需要第三步的前提条件是既要终止行号要大于起始行号,而且要终止列号要大于起始列号。同理,需要第四步的条件是至少有三行两列,也就是终止行号比起始行号至少大2,同时终止列号大于起始列号。综上,PrintMatrixInCircle的参考代码如下:
void PrintMatrixInCircle(int** numbers, int columns, int rows, int start)
{
int endX = columns – 1 – start;
int endY = rows – 1 – start;
// 从左到右打印一行
for (int i = start; i <= endX; ++i)
{
int number = numbers[start][i];
printNumber(number);
}
// 从上到下打印一列
if (start < endY)
{
for (int i = start + 1; i <= endY; ++i)
{
int number = numbers[i][endX];
printNumber(number);
}
}
// 从右到左打印一行
if (start < endX && start < endY)
{
for (int i = endX – 1; i >= start; –i)
{
int number = numbers[endY][i];
printNumber(number);
}
}
// 从下到上打印一行
if (start < endX && start < endY – 1)
{
for (int i = endY – 1; i >= start + 1; –i)
{
int number = numbers[i][start];
printNumber(number);
}
}
}
整理自《剑指Offer》及何海涛博客