我們一起聊聊順時針打印矩陣
梳理思路
當(dāng)我們遇到一個復(fù)雜的問題時,可以通過舉例將它畫出來,這樣就可以更直觀的發(fā)現(xiàn)規(guī)律。那么我們就先構(gòu)造一個矩陣出來,如下所示:
const matrix = [
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
[13, 14, 15, 16]
];
順時針訪問一個矩陣,那么它的訪問過程就如下圖所示:
觀察上圖后,我們可以很明顯的知道可以通過一個循環(huán)來打印這個矩陣,每次打印矩陣的一個圈,那么循環(huán)的終止條件是什么呢?
接下來,我們就來分析下循環(huán)的終止條件。假設(shè)矩陣的行數(shù)為rows,列數(shù)為cols,打印第一圈的左上角坐標(biāo)是(0,0),第二圈的左上角坐標(biāo)是(1,1),以此類推,我們注意到左上角的坐標(biāo)中,行標(biāo)與列標(biāo)總是相同的,于是可以在矩陣中選取左上角為(start,start)的一圈作為我們的分析目標(biāo)。
我們再來多列舉幾個例子觀察下,例如:
- 對于5*5的矩陣而言,最后一圈只有1個數(shù)字,對應(yīng)的坐標(biāo)為(2,2)
- 對于6*6的矩陣而言,最后一圈有4個數(shù)字,其左上角的坐標(biāo)依然為(2,2)
據(jù)上所述,我們可以發(fā)現(xiàn):5 > 2 * 2、6 > 2 * 2?全部成立,于是可以得出讓循環(huán)終止的條件為:cols > start * 2 && rows > start * 2。
接下來,我們來分析下如何實現(xiàn)打印一圈,前面的分析中我們已經(jīng)知道了打印1圈需要4步,即:
- 從左到右打印一行
- 從上到下打印一列
- 從右到左打印一行
- 從下到上打印一列
每一步我們根據(jù)起始坐標(biāo)和終止坐標(biāo)用一個循環(huán)就能打印出一行或者一列,但是最后一圈有可能退化成只有一行、只有一列,甚至只有一個數(shù)字,因此打印這樣的一圈就不再需要四步??赡苤恍枰?、兩步甚至一步。
我們來分析下每一步的執(zhí)行條件:
- 第一步是必須的,因為打印一圈至少有一步
start作為行坐標(biāo)
從start位置開始遍歷至終止列號,將其作為列坐標(biāo)
輸出每一個元素
- 第二步要求圈內(nèi)至少有2行,即:終止行號大于起始行號
從start+1位置遍歷至至終止行號,將其作為行坐標(biāo)
終止列號作為列坐標(biāo)
輸出每一個元素
- 第三步要求圈內(nèi)至少有兩行兩列,即:終止行號大于起始行號且終止列號大于起始列號
從終止列號-1位置遍歷至start,將其作為列坐標(biāo)
終止行號作為行坐標(biāo)
輸出每一個元素
- 第四步要求圈內(nèi)至少有三行兩列,即:終止行號比起始行號至少大2,同時終止列號大于起始列號
從終止行號-1位置遍歷至start+1位置,將其作為行坐標(biāo)
start作為列坐標(biāo)
輸出每一個元素
實現(xiàn)代碼
經(jīng)過上面的分析,我們已經(jīng)有了縝密的邏輯,接下來我們就可以愉快地進行編碼了,如下所示:
// 順時針打印矩陣
export function PrintMatrix<T>(
matrix: Array<Array<T>>,
cols: number,
rows: number
): void {
if (matrix == null || cols == null || rows == null) return;
// 圈數(shù)
let start = 0;
while (cols > start * 2 && rows > start * 2) {
// 打印每一圈的數(shù)據(jù)
PrintMatrixInCircle(matrix, cols, rows, start);
start++;
}
}
// 打印矩陣的一圈
function PrintMatrixInCircle<T>(
matrix: Array<Array<T>>,
cols: number,
rows: number,
start: number
): void {
// 計算當(dāng)前圈結(jié)束點坐標(biāo)(索引從0開始,所以需要-1)
// 終止列號
const endX = cols - 1 - start;
// 終止行號
const endY = rows - 1 - start;
// 從左到右打印一行
for (let i = start; i <= endX; i++) {
console.log(matrix[start][i]);
}
// 從上到下打印一列
if (start < endY) {
// 此時:
// 最后一列已經(jīng)在從左到右的打印中讀取了
for (let i = start + 1; i <= endY; i++) {
console.log(matrix[i][endX]);
}
}
// 從右到左打印一行
if (start < endX && start < endY) {
// 此時:
// 最后一列已經(jīng)在從上到下的打印中讀取了
for (let i = endX - 1; i >= start; i--) {
console.log(matrix[endY][i]);
}
}
// 從下到上打印一列
if (start < endX && start < endY - 1) {
// 此時:
// 最后一列已經(jīng)在從上到下的打印中讀取了
// 第一列的打印已經(jīng)在從左到右的打印中讀取了
for (let i = endY - 1; i >= start + 1; i--) {
console.log(matrix[i][start]);
}
}
}
我們用前面所舉的例子來驗證下上述代碼能否正常執(zhí)行,如下所示:
const matrix = [
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
[13, 14, 15, 16]
];
PrintMatrix(matrix, 4, 4);
示例代碼
本文所用代碼完整版請移步:
- PrintMatrix.ts
- printMatrix-test.ts