本文讨论算法题中常见的二维数组、矩阵的打印问题
1·矩阵从外到内螺旋打印
顺时针从外到内螺旋打印矩阵,例如输入如下的矩阵:
1 2 3
4 5 6
7 8 9
打印1 2 3 6 9 8 7 4 5
对于这样的问题,如果我们每个点逐个判断下一个打印哪个点,会比较麻烦。因此可以考虑每次确定一个范围,例如左上角(记为start)和右下角(记为end)的位置坐标,那么每次打印这两个点确定的矩阵的最外面那一圈,接着左上角的点向右下移动,右下角的点向左上移动,直到start的坐标比end“更右”或“更下”。
还是以上面的矩阵为例,首先左上角位于[0][0]也就是数字1的位置,右下角对应[2][2]也就是9的位置,那么打印二者确定的这一圈元素。接着左上角向右下移动到[1][1],右下角向左上移动到[1][1]位置,打印此时确定的一圈元素,实际上只有5这个数字。之后再移动,左上[2][2],右下[0][0],显然此时二者已经交错,说明打印完成。
因此,终止的条件就是:
startR >endR || startC >endC
左上角点的行号大于右下角点行号,或左上角点列号大于右下角点列号。
在达到这个条件之前,我们打印有这两个点(或者四个变量)确定的最外圈元素,接着移动这两个点。
对于一圈元素,一般情况,对应一个行列数都大于1的矩阵,此时可以分成四部分(四条边)分别打印即可。但是对于最里面一圈的元素,观察可知,对于列数多于行数的矩阵,最后会剩下1行;行数大于列数的矩阵,最后会剩下一列;行列数一样且为奇数的方阵,最后剩下一个(也可以看做一行)。因此,打印一圈元素这个函数需要分类讨论,避免出错。
C++代码实现如下:
void printOneCircle(vector<vector<int>>&matrix, int sr, int sc, int er, int ec) {
int r, c;
if (sr == er) {
r = sr;
for(c=sc;c<=ec;++c) cout<< matrix[r][c] << " ";
}
else if (sc == ec) {
c = sc;
for (r = sr; r <= er; ++r) cout << matrix[r][c] << " ";
}
else {
r = sr;
for (c = sc; c < ec; ++c) cout << matrix[r][c] << " ";
c = ec;
for (r = sr; r < er; ++r) cout << matrix[r][c] << " ";
r = er;
for (c = ec; c > sc; --c) cout << matrix[r][c] << " ";
c = sc;
for (r = er; r > sr; --r) cout << matrix[r][c] << " ";
}
//cout << endl;
}
void printMatSpiral(vector<vector<int>>&matrix) {
if (matrix.empty())return;
int rows = matrix.size(), cols = matrix[0].size();
int startR = 0, startC = 0, endR = rows-1, endC = cols-1;
while (startR <=endR && startC <=endC) {
printOneCircle(matrix, startR, startC, endR, endC);
++startR;
++startC;
--endC;
--endR;
//cout << "startR: "<< startR<<" startC: " << startC << endl;
//cout << "endR: " << endR << " endC: " << endC << endl;
}
}
函数printOneCircle就是打印一圈的函数,参数包括矩阵本身,左上角行、列号,右下角行、列号。具体实现时,先判断是否为一行或一列,是的话打印完这一行或一列即可,否则打印四条边,从左到右、上到下、右到左、下到上。这么做主要是为了方便处理四个角上的点。
2·矩阵Zig-Zag打印
同样例如如下矩阵:
1 2 3
4 5 6
7 8 9
要求按照1 4 2 3 5 7 8 6 9的顺序打印,延副对角线方向,先右上到左下,再左下到右上交替的顺序打印。这道题思路与上题类似,都是要先确定每一轮打印的起止点或范围,打印后确定下一个范围,重复直到全部打印完成。
那么观察可知,这种打印方式无论方向,起止点都是左下和右上的点,左下的点先向下移动,到达最后一行后向右,右上的点先向右移动,到达最后一列后向下移动,直到二者都到达矩阵右下角的点终止。
对于上面的矩阵,第一次起止点都位于[0][0],也就是包含数字1;接着是 4,2这条线,然后3,7这条线,然后是6,8,最后9,打印结束。
C++代码如下:
void printOneLine(vector<vector<int>>&matrix, int sr, int sc, int er, int ec,bool stoe) {
if (stoe) {
int r = sr, c = sc;
while (r <= er && c >= ec) {
cout << matrix[r][c] << " ";
++r;
--c;
}
}
else {
int r = er, c = ec;
while (r >= sr && c <= sc) {
cout << matrix[r][c] << " ";
--r;
++c;
}
}
//cout << endl;
}
void printMatDiagonalZigzag(vector<vector<int>>&matrix) {
if (matrix.empty())return;
int rows = matrix.size(), cols = matrix[0].size();
int startR = 0, startC = 0, endR = 0, endC = 0;
bool stoe = true;
while (startR < rows && startC < cols && endR < rows && endC < cols) {
printOneLine(matrix, startR, startC, endR, endC, stoe);
startR = (startC == cols - 1 ? startR + 1 : startR);
startC = (startC == cols - 1 ? startC : startC+1);
endC = (endR == rows - 1 ? endC + 1 : endC);
endR = (endR == rows - 1 ? endR : endR + 1);
stoe = !stoe;
//cout << "startR: "<< startR<<" startC: " << startC << endl;
//cout << "endR: " << endR << " endC: " << endC << endl;
}
}
注意这里我们在更新下一行的行列位置时,对于右上角点,由于是先向右移动后向下移动,以当前列数是否在最后一列,作为向下还是向右的标志,所以先更新行,后更新列,否则如果先更新列,在倒数第二列时,列数先增加,在更新行号时就会认为当前已在最后一列因此行数也要加一,这样就会出错。而对于左下角点,类似的,由于是先向下后向右,以当前行数是否为最后一行作为标志,要先更新列数,再更新行数。
由于要求Zig-Zag方式,这里对每一行传入一个bool型变量判断打印的方向。
总结来说,这一类问题不适合逐点判断,而是定义一组宏观的参数,每次对这组参数确定的部分打印,然后更新这组参数。要注意参数的更新策略以及终止条件是否正确,还有打印每一部分时有无特殊情况等。