1. 程式人生 > >使用 JS 輸出螺旋矩陣

使用 JS 輸出螺旋矩陣

關於螺旋矩陣

這是我曾經遇到過的面試題,在 LeetCode 上找到了題目的原型,難度中等。題目描述如下:

給定一個包含 m x n 個元素的矩陣(m 行, n 列),請按照順時針螺旋順序,返回矩陣中的所有元素。

示例 1:

輸入:
[
 [ 1, 2, 3 ],
 [ 4, 5, 6 ],
 [ 7, 8, 9 ]
]
輸出: [1,2,3,6,9,8,7,4,5]

示例 2:

輸入:
[
  [1, 2, 3, 4],
  [5, 6, 7, 8],
  [9,10,11,12]
]
輸出: [1,2,3,4,8,12,11,10,9,5,6,7]

解題思路

[[1, 1, 1, 1, 1, 1, 1],
 [1, 2, 2, 2, 2, 2, 1],
 [1, 2, 3, 3, 3, 2, 1],
 [1, 2, 2, 2, 2, 2, 1],
 [1, 1, 1, 1, 1, 1, 1]]

這是一道難度中等的題目,但是第一次看到題目時還是有一些困惑,不過仔細分析後很容易找到思路。比較直觀的思路是逐層法,從外向內迴圈每一層。其中單層迴圈的方法也有很多,我使用了插入法迴圈每一層。以 4X4 矩陣為例:

/**
 * --------------------------------------------
 * 以 4X4 矩陣為例
 * 
 * [[ 1, 2, 3, 4],
 *  [ 5, 6, 7, 8],
 *  [ 9,10,11,12],
 *  [13,14,15,16]]
 *
 * --------------------------------------------
 * 迴圈第一層
 * 
 * [[ 1, 2, 3, 4],  |
 *  [ 5,       8],  |
 *  [ 9,      12],  |
 *  [13,14,15,16]]  ▼
 *
 * --------------------------------------------
 * 將元素按順序插入陣列,`|` 表示插入位置
 * 
 * [ 1, 2, 3, 4, |]
 *         (8, 5)┘
 *         
 * [ 1, 2, 3, 4, 8, |, 5]
 *           (12, 9)┘
 *           
 * [ 1, 2, 3, 4, 8, 12, |, 9, 5]
 *      (16, 15, 14, 13)┘
 *      
 * [ 1, 2, 3, 4, 8, 12, 16, 15, 14, 13, 9, 5]
 * 
 * 第一層迴圈結束
 * 
 */

通過以上步驟拆分,可以看到輸出螺旋矩陣還是比較容易的,以下是具體的 JS 程式碼。

/**
 * @param {number[][]} matrix
 * @return {number[]}
 */
var spiralOrder = function(matrix) {
    // 最終返回的結果陣列
    var ans = [];

    var spiralLoop = function() {
        // 臨時陣列
        var arr = [];

        for (var i = 0; i < matrix.length; i++) {
            // 矩陣第一行
            if (i === 0) {
                arr = arr.concat(matrix[0]);
            }
            // 矩陣中間行
            if (i > 0 && i < matrix.length - 1) {
                var insertRight = matrix[0].length === 1 ? 
                                  [] : 
                                  arr.splice(-(i - 1), i - 1), // 插入位置右側的元素
                    last = matrix[i].splice(-1, 1), // 中間行尾元素
                    first = matrix[i].splice(0, 1); // 中間行首元素
                // 在指定位置插入元素
                arr = arr.concat(last, first, insertRight);
            }
            // 矩陣最後一行
            if (matrix.length > 1 && i === matrix.length - 1) {
                var insertRight  = matrix[0].length === 1 ? 
                                   [] : 
                                   arr.splice(-(matrix.length - 2), matrix.length - 2);
                // 將最後一行倒敘排列然後插入指定位置
                arr = arr.concat(matrix[matrix.length - 1].reverse(), insertRight);
            }

        }
        // 刪除矩陣的首尾行,得到的就是下一次需要遍歷的矩陣
        matrix.splice(0, 1);
        matrix.splice(-1, 1);

        ans = ans.concat(arr);
        // 根據矩陣內是否還存在陣列進行遞迴
        if (matrix.length >= 1) {
            spiralLoop(matrix);
        }

    }

    spiralLoop(matrix);

    return ans;

};

以上程式的執行時間大約在 60 ms 左右,超過所有提交答案的五成,中規中矩吧,距離最優演算法還有一定差距。

官方答案

LeetCode 原站給出了這道題的解題思路及程式碼,中文站則沒有。官方介紹了兩種方法,一種是模擬法,另一種是逐層法,其中逐層法的思路和我的思路是相同的,不過第一層迴圈的方法不同。對於二維矩陣的題目,我最先想到的也是模擬法,也就是模擬行走路線及方向,但是因為判斷條件有點複雜而放棄了。具體實現可以看官網文章 https://leetcode.com/articles/spiral-matrix/,以下是兩種方法的 python 實現,因時間關係,我就不寫 JS 版本了,後續有時間再補上,感興趣的博友可以自己轉換。

1、模擬法

class Solution(object):
    def spiralOrder(self, matrix):
        if not matrix: return []
        R, C = len(matrix), len(matrix[0])
        seen = [[False] * C for _ in matrix]
        ans = []
        dr = [0, 1, 0, -1]
        dc = [1, 0, -1, 0]
        r = c = di = 0
        for _ in range(R * C):
            ans.append(matrix[r][c])
            seen[r][c] = True
            cr, cc = r + dr[di], c + dc[di]
            if 0 <= cr < R and 0 <= cc < C and not seen[cr][cc]:
                r, c = cr, cc
            else:
                di = (di + 1) % 4
                r, c = r + dr[di], c + dc[di]
        return ans

2、逐層法

class Solution(object):
    def spiralOrder(self, matrix):
        def spiral_coords(r1, c1, r2, c2):
            for c in range(c1, c2 + 1):
                yield r1, c
            for r in range(r1 + 1, r2 + 1):
                yield r, c2
            if r1 < r2 and c1 < c2:
                for c in range(c2 - 1, c1, -1):
                    yield r2, c
                for r in range(r2, r1, -1):
                    yield r, c1

        if not matrix: return []
        ans = []
        r1, r2 = 0, len(matrix) - 1
        c1, c2 = 0, len(matrix[0]) - 1
        while r1 <= r2 and c1 <= c2:
            for r, c in spiral_coords(r1, c1, r2, c2):
                ans.append(matrix[r][c])
            r1 += 1; r2 -= 1
            c1 += 1; c2 -= 1
        return ans