← 題庫 / Archive
2026-05-09 Daily Medium ArrayMatrixSimulation

1914. Cyclically Rotating a Grid

題目 / Problem

中文: 給定一個 m x n 的整數矩陣 grid,其中 mn 都是偶數,以及一個整數 k。矩陣由若干「層」(layer) 組成,從外到內每層形成一個矩形環。每次旋轉時,每一層中的元素會被「逆時針方向相鄰元素」取代;將整個矩陣執行 k 次這樣的旋轉,回傳結果矩陣。

English: You are given an m x n integer matrix grid (both m and n are even) and an integer k. The matrix consists of concentric rectangular layers. In one rotation, every element of a layer moves into the place of its counter-clockwise neighbour. Perform k such rotations on every layer and return the resulting matrix.

Constraints - 2 <= m, n <= 50,且 m、n 皆為偶數 / both even - 1 <= grid[i][j] <= 5000 - 1 <= k <= 10^9 (k 可能很大 / k can be huge)

Example

Input:  grid = [[40,10],[30,20]], k = 1
Output: [[10,20],[40,30]]

唯一的一層 [40,10,20,30] (順時針) 逆時針旋轉一格後變成 [10,20,30,40],寫回矩陣即得答案。 The single layer [40,10,20,30] (clockwise) rotates one step counter-clockwise to [10,20,30,40], which gives the answer when written back.

名詞解釋 / Glossary

  • Layer / 層:矩陣中由外到內第 d 個矩形邊框,範圍是 row ∈ [d, m-1-d],col ∈ [d, n-1-d] 的最外圈。一個 m×n 矩陣共有 min(m,n)/2 層。 / The d-th rectangular ring counted from the outside; there are min(m,n)/2 such rings.
  • Cyclic rotation / 循環旋轉:把序列當成一個首尾相接的環,每個元素往同一方向移動一格;最後一個會繞回到第一個。 / Treat a sequence as a ring; every element shifts one step in the same direction and the last wraps around to the first.
  • Counter-clockwise / 逆時針:沿環移動時,左上角往左下角(再到右下、右上)的方向。本題中元素「占據逆時針方向相鄰元素的位置」。 / Direction opposite to a clock's hands: top-left → bottom-left → bottom-right → top-right.
  • Modulo / 取餘 (%):a % ba 除以 b 的餘數,常用於把任意整數壓進 [0, b-1] 範圍。本題用 k % L 把超大的 k 壓成「實際有效的旋轉次數」(因為轉 L 次等於沒轉)。 / The remainder of a / b; we use k % L because rotating a length-L ring by L positions is the identity.
  • In-place / 原地修改:不額外複製整個矩陣,直接在輸入上改寫;只用一條長度為 L 的暫存陣列存當前這一層。 / Mutate the input directly; we only allocate one temporary array per layer (length = layer perimeter).
  • Perimeter of a layer / 層的周長:對第 d 層,長度為 2 * ((m-2d) + (n-2d) - 2);最內層至少有 4 個元素(因 m, n ≥ 2 且為偶數)。 / 2 * ((m-2d) + (n-2d) - 2); even the innermost ring has at least 4 elements.

思路

最直觀的做法是「轉一次就重排一次」,但 k 可達 10^9,逐次模擬會超時。關鍵觀察是:每一層都是獨立的一個環,環的長度 L 固定;旋轉 L 次會回到原狀,所以實際只需要旋轉 k % L 次。於是問題拆成兩步:先把每一層「攤平」成一個一維陣列 arr,再把陣列做一次循環位移後寫回原來的位置。攤平的順序我們選順時針(從左上角開始,沿上邊→右邊→下邊→左邊)。對應地,逆時針旋轉一格在這個順時針陣列上就相當於「左移一格」:新的 pos[i] 應該等於舊的 pos[(i+1) mod L]。所以旋轉 k 次後,寫回時用公式 new[i] = old[(i + k%L) mod L]。為了避免 kL 大、或讀取/寫入時搞混順序,我們先把整層複製進 arr、把 kL 取餘,然後用同樣的順時針順序遍歷一次寫回去——這樣讀寫位置一一對應,不會錯位。重複處理每一層即可,每個元素只被讀一次寫一次,效率最佳。

The naive approach—rotate one step at a time and repeat k times—is too slow because k can reach 10^9. The key insight is that each layer is an independent cyclic ring of fixed length L, and rotating a length-L ring by L is a no-op, so the effective number of rotations is k % L. With that in hand, processing one layer becomes a flatten-rotate-restore sandwich: linearise the layer into a 1-D buffer arr (we walk it clockwise: top row left→right, right column top→bottom, bottom row right→left, left column bottom→top), shift, then write back. A counter-clockwise step on the ring corresponds to a left shift in our clockwise array, so after k rotations the new value at position i is arr[(i + k%L) % L]. We traverse the layer in the same clockwise order on the way back, which guarantees every read/write maps to the correct cell. Loop this over all min(m,n)/2 layers and we are done. Each grid cell is touched a constant number of times, so the runtime is O(m·n).

逐步走查 / Walkthrough

範例 / Example: grid = [[40,10],[30,20]], k = 1.

只有一層,d = 0,top=0, bottom=1, left=0, right=1,周長 L = 2*(1) + 2*(1) = 4,kk = 1 % 4 = 1。 There is only one layer with top=0, bottom=1, left=0, right=1, perimeter L = 4, effective shift kk = 1.

Step 1 — Flatten clockwise / 順時針攤平

idx 來源座標 / source cell 值 / value
0 grid[0][0] 40
1 grid[0][1] 10
2 grid[1][1] 20
3 grid[1][0] 30

所以 arr = [40, 10, 20, 30]

Step 2 — Compute the shifted index / 計算位移後的索引

寫回 pos[i] 時取 arr[(i + kk) % 4] = arr[(i + 1) % 4]

寫回 idx i 目標座標 / target 取自 / read from 寫入值 / value
0 grid[0][0] arr[1] 10
1 grid[0][1] arr[2] 20
2 grid[1][1] arr[3] 30
3 grid[1][0] arr[0] 40

Step 3 — Result / 結果

grid = [[10, 20], [40, 30]] ✓ 與題目輸出一致 / matches the expected output.

Solution — C

// 演算法 / Algorithm:
//   1) 對每一層,從左上角依順時針順序把所有元素攤平成一維陣列 arr。
//      For each layer, flatten the ring clockwise (top→right→bottom→left) into arr.
//   2) 因為轉 L 次回到原狀,實際旋轉次數是 kk = k % L。
//      Effective rotation is kk = k % L since rotating L times is identity.
//   3) 用同樣的順時針順序寫回:new[i] = arr[(i + kk) % L]。
//      Write back in the same clockwise order using new[i] = arr[(i + kk) % L].

#include <stdlib.h>  // malloc / free,以便動態配置暫存陣列 / for malloc/free

int** rotateGrid(int** grid, int gridSize, int* gridColSize, int k,
                 int* returnSize, int** returnColumnSizes) {
    int m = gridSize;            // 矩陣列數 / number of rows
    int n = gridColSize[0];      // 矩陣行數 / number of columns (every row same length)

    // 層數 = min(m, n) / 2;m, n 皆為偶數,所以一定整除。
    // Number of layers = min(m, n) / 2 (exact since m, n are even).
    int layers = (m < n ? m : n) / 2;

    for (int d = 0; d < layers; d++) {
        // 第 d 層的四個邊界 / four boundaries of layer d
        int top = d, bottom = m - 1 - d;
        int left = d, right = n - 1 - d;

        // 周長 = 兩條垂直邊 + 兩條水平邊;再扣掉重複算的 4 個角。
        // Perimeter: 2*(height) + 2*(width) - 4, written here in expanded form.
        int L = 2 * (bottom - top) + 2 * (right - left);

        // 暫存這一層的元素;只配置長度 L,用完即釋放。
        // Temporary buffer for this layer; freed at the end of the loop body.
        int* arr = (int*)malloc(sizeof(int) * L);

        // ---- 攤平:順時針把這一層複製到 arr ----
        // ---- Flatten the ring clockwise into arr ----
        int idx = 0;
        // 上邊:由左到右 / top edge: left → right
        for (int j = left; j <= right; j++)        arr[idx++] = grid[top][j];
        // 右邊:由上到下(跳過已加入的右上角)/ right edge: top+1 → bottom
        for (int i = top + 1; i <= bottom; i++)    arr[idx++] = grid[i][right];
        // 下邊:由右到左(跳過已加入的右下角)/ bottom edge: right-1 → left
        for (int j = right - 1; j >= left; j--)    arr[idx++] = grid[bottom][j];
        // 左邊:由下到上(跳過已加入的左下角與左上角)/ left edge: bottom-1 → top+1
        for (int i = bottom - 1; i > top; i--)     arr[idx++] = grid[i][left];

        // 把超大的 k 壓成有效旋轉次數;否則迴圈會浪費時間。
        // Reduce k modulo L; without this, large k would blow up the runtime.
        int kk = k % L;

        // ---- 寫回:同樣順時針走一遍,讀 arr[(i + kk) % L] ----
        // ---- Restore: walk the same clockwise path, but read shifted indices ----
        idx = 0;
        for (int j = left; j <= right; j++)
            grid[top][j]    = arr[(idx++ + kk) % L];   // 上邊 / top edge
        for (int i = top + 1; i <= bottom; i++)
            grid[i][right]  = arr[(idx++ + kk) % L];   // 右邊 / right edge
        for (int j = right - 1; j >= left; j--)
            grid[bottom][j] = arr[(idx++ + kk) % L];   // 下邊 / bottom edge
        for (int i = bottom - 1; i > top; i--)
            grid[i][left]   = arr[(idx++ + kk) % L];   // 左邊 / left edge

        free(arr);   // 釋放這層的暫存記憶體 / release the per-layer buffer
    }

    // LeetCode 的 C 介面要求回傳列數與每列長度。
    // The LeetCode C harness asks for the number of rows and the length of each row.
    *returnSize = m;
    *returnColumnSizes = (int*)malloc(sizeof(int) * m);
    for (int i = 0; i < m; i++) (*returnColumnSizes)[i] = n;

    return grid;   // 我們是原地修改,所以可以直接回傳同一個指標 / in-place, return same ptr
}

Solution — C++

// 演算法與 C 版完全相同:對每層攤平 → 用 k % L 做循環位移 → 順時針寫回。
// Same algorithm as the C version: flatten each layer, shift by k % L, write back clockwise.
// 這裡用 std::vector 取代手動 malloc/free,讓程式更短也更安全。
// Here we use std::vector instead of manual malloc/free for safer, terser code.

#include <vector>   // std::vector,自動管理記憶體的動態陣列 / dynamic array w/ auto memory mgmt
using std::vector;

class Solution {
public:
    vector<vector<int>> rotateGrid(vector<vector<int>>& grid, int k) {
        int m = grid.size();           // 列數 / row count
        int n = grid[0].size();        // 行數 / column count
        int layers = std::min(m, n) / 2;  // 層數 / number of layers

        for (int d = 0; d < layers; ++d) {
            // 當前層的四個邊界 / four boundaries of layer d
            int top = d, bottom = m - 1 - d;
            int left = d, right = n - 1 - d;
            int L = 2 * (bottom - top) + 2 * (right - left);  // 周長 / perimeter

            // vector<int> 是 STL 動態陣列;reserve 預先配置容量,避免多次擴容。
            // vector<int> is the STL dynamic array; reserve preallocates capacity.
            vector<int> arr;
            arr.reserve(L);

            // ---- 攤平:順時針把這一層 push 進 arr ----
            // ---- Flatten the ring clockwise via push_back ----
            for (int j = left; j <= right; ++j)      arr.push_back(grid[top][j]);
            for (int i = top + 1; i <= bottom; ++i)  arr.push_back(grid[i][right]);
            for (int j = right - 1; j >= left; --j)  arr.push_back(grid[bottom][j]);
            for (int i = bottom - 1; i > top; --i)   arr.push_back(grid[i][left]);

            int kk = k % L;   // 有效旋轉次數 / effective shift

            // ---- 順時針寫回,讀位移後的索引 ----
            // ---- Walk the same clockwise path and read shifted positions ----
            int idx = 0;
            for (int j = left; j <= right; ++j)
                grid[top][j]    = arr[(idx++ + kk) % L];
            for (int i = top + 1; i <= bottom; ++i)
                grid[i][right]  = arr[(idx++ + kk) % L];
            for (int j = right - 1; j >= left; --j)
                grid[bottom][j] = arr[(idx++ + kk) % L];
            for (int i = bottom - 1; i > top; --i)
                grid[i][left]   = arr[(idx++ + kk) % L];
            // 迴圈結束,arr 自動被解構釋放;不需要手動 free。
            // arr is destroyed automatically at scope exit — no manual free required.
        }

        return grid;   // 已就地修改,直接回傳 / mutated in place, return as-is
    }
};

複雜度 / Complexity

  • Time: O(m · n) — 每個格子在攤平和寫回時各被存取一次,層的總周長加起來等於矩陣總格數;k % L 把大 k 一次壓掉,不會再多花時間。 / Each cell is visited a constant number of times; the sum of all layer perimeters equals m·n, and k % L collapses the huge k upfront.
  • Space: O(max(m, n)) — 暫存陣列 arr 一次只儲存一層,最外層的周長為 2(m+n)-4,即 O(m+n)。回傳的矩陣是原本的 grid,不額外計算。 / The per-layer buffer holds at most the outermost perimeter, 2(m+n)-4. The output reuses grid, so it is not counted.

Pitfalls & Edge Cases

  • 忘記取 k % L / Forgetting k % L:k 可達 10^9,逐次模擬必 TLE;不同層 L 不同,要分別取餘。 / Without it, brute simulation TLEs; remember L differs per layer, so compute kk inside the loop.
  • 方向搞反 / Direction confusion:題目是「逆時針」旋轉,但我們用「順時針」攤平 + 「索引加 kk」來實現;若改成索引減 kk,會變成順時針旋轉,結果錯誤。 / The puzzle rotates counter-clockwise, but we flatten clockwise; the equivalent operation is adding kk to the index. Subtracting would rotate the wrong way.
  • 角落重複 / Double-counting corners:四條邊接在一起時要避免把角落讀兩次。攤平迴圈把每一邊的起點向內縮一格(top+1right-1bottom-1、條件 i > top)就避開了。 / The four edge loops use offset start indices so each corner is touched exactly once.
  • 最內層的退化情形 / Degenerate innermost layer:當 m = n = 2 時只有一層、L = 4,程式仍正常工作,因為四條邊長度都至少為 1。 / Even with m = n = 2 the formulas give L = 4 and all loops execute correctly.
  • 回傳介面 (僅 C) / Return interface (C only):LeetCode 的 C 簽名要求填 *returnSize 與配置 *returnColumnSizes。漏掉任一個會在判題時 segfault。 / Forgetting to set *returnSize or to allocate *returnColumnSizes will crash the judge.
  • 不要複製整個矩陣 / Don't deep-copy the grid:既然每層之間互不影響,逐層原地改寫就足夠;否則會多花一份 O(m·n) 空間。 / Layers are independent; mutate in place to save memory.