6.5陣列--模擬、偏移量-螺旋矩陣

七龙猪發表於2024-06-05

M:59.螺旋矩陣II

題意描述

給你一個正整數 n ,生成一個包含 1n^2 所有元素,且元素按順時針順序螺旋排列的 n x n 正方形矩陣 matrix

示例 1:

img

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

示例 2:

輸入:n = 1
輸出:[[1]]

提示:

  • 1 <= n <= 20

思路

這道題目可以說在面試中出現頻率較高的題目,本題並不涉及到什麼演算法,就是模擬過程,但卻十分考察對程式碼的掌控能力。

要如何畫出這個螺旋排列的正方形矩陣呢?

求解本題依然是要堅持迴圈不變數原則。

模擬順時針畫矩陣的過程:

  • 填充上行從左到右
  • 填充右列從上到下
  • 填充下行從右到左
  • 填充左列從下到上

由外向內一圈一圈這麼畫下去。

這裡一圈下來,我們要畫每四條邊,這四條邊怎麼畫,每畫一條邊都要堅持一致的左閉右開,或者左開右閉的原則,這樣這一圈才能按照統一的規則畫下來。

那麼我按照左閉右開的原則,來畫一圈,大家看一下:

img

這裡每一種顏色,代表一條邊,我們遍歷的長度,可以看出每一個拐角處的處理規則,拐角處讓給新的一條邊來繼續畫。

這也是堅持了每條邊左閉右開的原則。

AC程式碼:

class Solution {
public:
    vector<vector<int>> generateMatrix(int n) {
        vector<vector<int>> res(n, vector<int>(n, 0)); // 使用vector定義一個二維陣列
        int startx = 0, starty = 0; // 定義每迴圈一個圈的起始位置
        int loop = n / 2; // 每個圈迴圈幾次,例如n為奇數3,那麼loop = 1 只是迴圈一圈,矩陣中間的值需要單獨處理
        int mid = n / 2; // 矩陣中間的位置,例如:n為3, 中間的位置就是(1,1),n為5,中間位置為(2, 2)
        int count = 1; // 用來給矩陣中每一個空格賦值
        int offset = 1; // 需要控制每一條邊遍歷的長度,每次迴圈右邊界收縮一位
        int i,j;
        while (loop --) {
            i = startx;
            j = starty;

            // 下面開始的四個for就是模擬轉了一圈
            // 模擬填充上行從左到右(左閉右開)
            for (j; j < n - offset; j++) {
                res[i][j] = count++;
            }
            // 模擬填充右列從上到下(左閉右開)
            for (i; i < n - offset; i++) {
                res[i][j] = count++;
            }
            // 模擬填充下行從右到左(左閉右開)
            for (; j > starty; j--) {
                res[i][j] = count++;
            }
            // 模擬填充左列從下到上(左閉右開)
            for (; i > startx; i--) {
                res[i][j] = count++;
            }

            // 第二圈開始的時候,起始位置要各自加1, 例如:第一圈起始位置是(0, 0),第二圈起始位置是(1, 1)
            startx++;
            starty++;

            // offset 控制每一圈裡每一條邊遍歷的長度
            offset += 1;
        }

        // 如果n為奇數的話,需要單獨給矩陣最中間的位置賦值
        if (n % 2) {
            res[mid][mid] = count;
        }
        return res;
    }
};

法二:偏移量法

class Solution {
public:
    vector<vector<int>> generateMatrix(int n) {
        vector<vector<int>> res(n , vector<int>(n , 0));
     
        //順序依次為左上右下,一開始向右,因此d=1, 列+1
        int dx[4] = {-1 , 0 , 1 , 0}, dy[4] = {0 , 1 , 0 , -1};
      //注意這裡迴圈是以填的數cnt:1~n*n來迴圈n^2次
        for(int cnt = 1 , x = 0 , y = 0 , d = 1 ;cnt <= n * n ; cnt++){
          res[x][y] = cnt;
          int a = x + dx[d] , b = y + dy[d];
          if(a < 0 || a >= n || b < 0 || b >= n || res[a][b]){
            d = (d + 1) % 4;
            a = x + dx[d] , b = y + dy[d];
          }
          x = a , y = b;
        }

        return res;
    }
};

ACwing756.蛇形矩陣

題目描述:

輸入兩個整數 𝑛 和 𝑚,輸出一個 𝑛 行 𝑚 列的矩陣,將數字 1 到 𝑛×𝑚 按照回字蛇形填充至矩陣中。

具體矩陣形式可參考樣例。

輸入格式

輸入共一行,包含兩個整數 𝑛 和 𝑚。

輸出格式

輸出滿足要求的矩陣。

矩陣佔 𝑛 行,每行包含 𝑚 個空格隔開的整數。

資料範圍

1≤𝑛,𝑚≤100

輸入樣例:

3 3

輸出樣例:

1 2 3
8 9 4
7 6 5

思路:

設當前位置座標為(x,y),上、下、左、右方向分別為dr=0 dr=2 dr=3 dr=1
則該位置上、下、左、右的位置所對應的偏移量分別為(x-1,y) (x+1,y) (x,y-1) (x,y+1)
將方向與偏移量的對應關係初始化為兩個陣列便於引用

1.png

每次執行迴圈後,判斷下一個位置是否到達陣列邊界,或陣列中已經存在元素
若滿足上述情況,則改變方向。

AC程式碼:

#include <bits/stdc++.h>
using namespace std;

const int maxn=110;

int a[maxn][maxn];  //定義空的二維陣列陣列
int dx[]={-1,0,1,0},dy[]={0,1,0,-1};  //初始化方向所對應的偏移量的陣列

int main()
{
    int n,m;
    cin>>n>>m;
    int dr=1,x=0,y=0;  //初始化開始方向為右,初始化開始的位置
    for(int i=1;i<=n*m;i++){
        a[x][y]=i;  //存入答案
        int h=x+dx[dr],l=y+dy[dr];  //定義臨時變數存放(x,y)的下一個位置的座標
        if(h<0||l<0||h>=n||l>=m||a[h][l]){  //判斷
            dr=(dr+1)%4;
            h=x+dx[dr],l=y+dy[dr];
        } 
        x=h,y=l;  //更新(x,y)
    }
    for(int i=0;i<n;i++){  //迴圈列印輸出
        for(int j=0;j<m;j++){
            cout<<a[i][j]<<" ";
        }
        cout<<endl;
    }
    return 0;
}


類似題目:ACwing3208.Z字形掃描

題意描述:

在影像編碼的演算法中,需要將一個給定的方形矩陣進行 𝑍 字形掃描(Zigzag Scan)。

給定一個𝑛×𝑛 的矩陣,𝑍 字形掃描的過程如下圖所示:

zig.png

對於下面的 4×4 的矩陣,

1 5 3 9
3 7 5 6
9 4 6 4
7 3 1 3

對其進行 𝑍 字形掃描後得到長度為 16 的序列:1 5 3 9 7 3 9 5 4 7 3 6 6 4 1 3

請實現一個 𝑍 字形掃描的程式,給定一個𝑛×𝑛 的矩陣,輸出對這個矩陣進行 𝑍 字形掃描的結果。

輸入格式

輸入的第一行包含一個整數 𝑛,表示矩陣的大小。

輸入的第二行到第 𝑛+1 行每行包含 𝑛 個正整數,由空格分隔,表示給定的矩陣。

輸出格式

輸出一行,包含 𝑛×𝑛 個整數,由空格分隔,表示輸入的矩陣經過 𝑍 字形掃描後的結果。

資料範圍

1≤𝑛≤500,
矩陣元素為不超過 1000 的正整數。

輸入樣例:

4
1 5 3 9
3 7 5 6
9 4 6 4
7 3 1 3

輸出樣例:

1 5 3 9 7 3 9 5 4 7 3 6 6 4 1 3

分析:

該題以Z字形遍歷陣列,對於奇數和偶數情況下,邊界轉向複雜
擴大原二維陣列,使邊界轉向統一

怕.png

觀察旋轉方向,設初始方向dr = 0
擴大二維陣列,遍歷滿足在原陣列範圍內時輸出

說的話.png

AC程式碼

#include <bits/stdc++.h>
using namespace std;
const int N=505;

int a[2*N][2*N];  //定義時直接擴大

int main(){
    int n;
    scanf("%d",&n);
    for(int i=0;i<n;i++){  //初始化二維陣列
        for(int j=0;j<n;j++){
            scanf("%d",&a[i][j]);
        }
    }
    int dr=0,dx[]={0,1,1,-1},dy[]={1,-1,0,1};  //定義(0,1)的方向dr=0  定義偏移量陣列
    printf("%d ",a[0][0]);  //先將(0,0)位置的數輸出
    int x=0,y=1;  //初始化位置為(0,1)
    for(int i=0;i<(2*n+1)*n;i++){  //迴圈遍歷擴大後的陣列
        if(x<n&&y<n){
            printf("%d ",a[x][y]);  //滿足在原始陣列範圍內輸出
        }
        int l=x+dx[dr],r=y+dy[dr];  //臨時變數判斷下一個要遍歷的格子座標(l,r)
        if(dr==0||dr==2||r<0||l<0||r>=n||l>=n){  //如果dr=0或dr=2或(l,r)出界時改變方向
            dr=(dr+1)%4;
            l=x+dx[dr],r=y+dy[dr];
        }
        x=l,y=r;  //更新(x,y)
    }
    return 0;
}


LCR 146. 螺旋遍歷二維陣列

題目描述:

給定一個二維陣列 array,請返回「螺旋遍歷」該陣列的結果。

螺旋遍歷:從左上角開始,按照 向右向下向左向上 的順序 依次 提取元素,然後再進入內部一層重複相同的步驟,直到提取完所有元素。

示例 1:

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

示例 2:

輸入:array  = [[1,2,3,4],[12,13,14,5],[11,16,15,6],[10,9,8,7]]
輸出:[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]

限制:

  • 0 <= array.length <= 100
  • 0 <= array[i].length <= 100

注意:本題與主站 54 題相同:https://leetcode-cn.com/problems/spiral-matrix/

思路1:模擬

AC程式碼:

class Solution {
public:
    vector<int> spiralArray(vector<vector<int>>& array)
    {
        if (array.empty()) return {};
        int l = 0, r = array[0].size() - 1, t = 0, b = array.size() - 1;
        vector<int> res;
        while(true)
        {
            for (int i = l; i <= r; i++) res.push_back(array[t][i]); // left to right
            if (++t > b) break;
            for (int i = t; i <= b; i++) res.push_back(array[i][r]); // top to bottom
            if (l > --r) break;
            for (int i = r; i >= l; i--) res.push_back(array[b][i]); // right to left
            if (t > --b) break;
            for (int i = b; i >= t; i--) res.push_back(array[i][l]); // bottom to top
            if (++l > r) break;
        }
        return res;
    }
};

思路2:偏移量法

判斷路徑是否進入之前訪問過的位置需要使用一個與輸入二維陣列大小相同的輔助二維陣列visited,其中的每個元素表示該位置是否被訪問過。當一個元素被訪問時,將 visited 中的對應位置的元素設為已訪問。

如何判斷路徑是否結束?由於二維陣列中的每個元素都被訪問一次,因此路徑的長度即為二維陣列中的元素數量,當路徑的長度達到二維陣列中的元素數量時即為完整路徑,將該路徑返回。

AC程式碼:

class Solution {
public:
    vector<int> spiralArray(vector<vector<int>>& array) {
        if(array.size() == 0  || array[0].size() == 0)  return {};
        
        int r = array.size(),c = array[0].size();
        vector<vector<bool>> visited(r , vector<bool>(c));
        int t = r * c;
    
        vector<int> res(t);
        int dx[] = {-1 , 0 , 1, 0}, dy[] = {0 , 1 , 0, -1};
        for(int i =0 ,a = 0 , b = 0 , d = 1; i < t ; i++ ){
            res[i] = array[a][b];
            visited[a][b] = true;
            int na = a + dx[d] , nb = b + dy[d];
            if(na< 0 || na >= r || nb < 0 || nb >= c || visited[na][nb]){
                d = (d + 1) % 4;
                na = a + dx[d] , nb = b + dy[d];
            }
            a = na , b = nb;  
        }
        return res;
    }
};

相關文章