# Ch23 矩陣 > 搭配 [Leetcode 解題系統](https://leetcode.com/) > 回目錄:[國立科學園區實驗中學C++程式語言自學講義](https://hackmd.io/s/B18yT_i5Z) ## <font color='darkblue'>矩陣 / 二維陣列</font> 之前所教過的陣列/vector,都是一維的 我們可以在一排格子裡儲存數值,用index來表示「哪一格」 這一章我們要學的是二維陣列,會用到「第幾列第幾行」的概念 宣告一個固定大小的二維陣列 ```cpp int a[50][100]; ``` 這樣一來 a 就是一個有 50\*100 格的二維陣列 ### 宣告 宣告一個二維的 vector ```cpp vector<vector<int>> a; ``` 這樣一來 a 就是一個空的二維 vector 像是 vector 包著 vector 的概念 ### push_back 我們可以增加新的列 ```cpp vector<int> b = {1, 2, 3}; a.push_back(b); ``` 這樣一來 a 的最後一列就會是 {1, 2, 3} 也可以對於某一列添加新的元素 ```cpp a[5].push_back(3); ``` 這樣一來 a 的第 5 列就會多一個新的數字 3 ### 賦值/取值 當我們想要把 a 的第 i 列第 j 行變成某數 x 時 可以寫成 ```cpp a[i][j] = x; ``` 同樣的,當我們想要取用 a 的第 i 列第 j 行的值時 可以寫成 ```cpp int y = a[i][j]; ``` ### 清空 當我們想要把名為 a 的二維 vector 整個清空 可以寫成 ```cpp a.clear(); ``` 若我們只是想把 a 的第 i 列清空 可以寫成 ```cpp a[i].clear(); ``` 這樣一來,其他列都還是原本的樣子 ### 長度 當我們想要知道 a 有幾列 可以寫成 ```cpp cout << a.size(); ``` 當我們想要知道 a 的第 i 列有多長 可以寫成 ```cpp cout << a[i].size(); ``` 不同於靜態的二維陣列 `int a[50][100]` 一定是固定的長寬 二維的 vector 的每一列可以是不同長度的 ## <font color='darkblue'>拜訪每個值</font> <font color='darkorange'>【例題】</font> > 題目給一個名為 accounts 的二維 vector > 裡面的每一列都代表某一個人的全部財產 > 例如某一列是 [1, 2, 3] ,就代表那個人有 1+2+3=6 元 > 請問最有錢的那個人有多少錢? 這是 Leetcode 上的題目,連結是 [Leetcode 1672](https://leetcode.com/problems/richest-customer-wealth/description/) 但凡是二維陣列的題目,我們通常都會要用雙層的迴圈來拜訪格子們 我們可以在迴圈的最外面用一個變數 `max_sum` 來代表最大值 中間用一個變數 `sum` 來計算和暫存每個人的財產 ```cpp= int max_sum = 0; for(int i=0;i<accounts.size();i++){ // 計算第 i 個人的財產 int sum = 0; for(int j=0;j<accounts[i].size();j++){ sum+=accounts[i][j]; } // 更新最大值 if(sum>max_sum) max_sum = sum; } return max_sum; ``` <font color="darkgreen"> 【學生練習題】</font> > - [ ] [Leetcode 2643: 最多 1 的那一列 ](https://leetcode.com/problems/row-with-maximum-ones/) > > 題目給我們一個名為 mat 的二維 vector,請問最多 1 的那一列是哪一列呢?他有幾個 1 呢? 注意這題要回傳的是一個一維 vector,要有兩格 第一格用來存「第幾列最多1」,第二格用來存「它有幾個1」 ## <font color='darkblue'>對角線上的格子</font> <font color='darkorange'>【例題】</font> > 題目給一個名為 mat 的二維 vector > 請問在"對角線"上的格子數字的總和是多少呢? 這是 Leetcode 上的題目,連結是 [Leetcode 1572](https://leetcode.com/problems/matrix-diagonal-sum/) 如果二維陣列的列數和行數相同,他就是一個正方形矩陣 如何判斷一個格子是不是在對角線上呢? 假設正方形邊長是 5 左上-右下的對角線有這些格子:(0, 0), (1, 1), (2, 2)... 右上-左下的對角線有這些格子:(0, 4), (1, 3), (2, 2)... 我們可以觀察出這樣的規律: 用 `(i, j)` 來代表某格子的座標 若 `i==j` 或是 `i+j==(n-1)`,則可知 `(i, j)` 在對角線上 ```cpp= int sum = 0; int n = mat.size(); // 正方形邊長 for(int i=0;i<n);i++){ for(int j=0;j<n;j++){ if(i==j||i+j==n-1) sum+=mat[i][j]; } } return sum; ``` 或是更有效率的方法 就是只拜訪對角線上的格子 ```cpp= int sum = 0; int n = mat.size(); // 正方形邊長 for(int i=0;i<n);i++){ // 左上右下對角線的格子 sum += mat[i][i]; // 右上左下對角線的格子 // 注意如果是正中央,會被重複加一次,要避免 if(n-1-i != i) sum+=mat[i][n-1-i]; } return sum; ``` 這樣只需要一層迴圈就能完成了 <font color="darkgreen"> 【學生練習題】</font> > - [ ] [Leetcode 2614: 對角線上最大的質數 ](https://leetcode.com/problems/prime-in-diagonal/) > > 所有在對角線上的格子裡,最大的「質數」是多少呢? 判斷質數有點麻煩,幸好我們在 [for 迴圈](https://hackmd.io/@CLKO/H1iyHQc2b?type=view#%E7%B5%84%E5%90%88%E6%8A%80%EF%BC%9Afor%E8%88%87if%E6%90%AD%E9%85%8D%E4%BD%BF%E7%94%A8) 的單元學了簡單的做法 或是可以直接照抄: ```cpp= bool is_prime(int n){ if(n==1) return false; for(int i=2;i*i<=n;i++){ if(n%i==0) return false; } return true; } ``` <font color="darkgreen"> 【學生練習題】</font> > - [ ] [Leetcode 2319: X 矩陣 ](https://leetcode.com/problems/check-if-matrix-is-x-matrix/) > > 題目給我們一個名為 grid 的二維 vector,請問是不是所有對角線上的數字都不是 0 ,且所有不在對角線上的數字都是 0 呢? > > 例如這個矩陣符合條件,要回傳 true > ![image](https://hackmd.io/_uploads/rkrD_8qDa.png) > > 例如這個矩陣就不符合條件,要回傳 false > ![image](https://hackmd.io/_uploads/r1Ti_IqPT.png) ## <font color='darkblue'>同一行或同一列的鄰居</font> <font color='darkorange'>【例題】</font> > 題目給一個名為 matrix 的二維 vector > 請找出所有的「幸運數字」 > 被認為「幸運」的條件是,這個數字是他那一橫列裡最小的,又是他那一直行裡最大的 這是 Leetcode 上的題目,連結是 [Leetcode 1380](https://leetcode.com/problems/lucky-numbers-in-a-matrix/) 我們可以拜訪 matrix 裡的每個數字 檢查和他同一橫列和同一直行裡有沒有哪位鄰居害他無法成為「幸運數字」的 如果都沒有,他就是幸運的 ```cpp= vector<int> ret; int num_of_rows = matrix.size(); int num_of_cols = matrix[0].size(); for(int i=0;i<num_of_rows;i++){ for(int j=0;j<num_of_cols;j++){ // 無論如何,先假定他是幸運的 bool lucky = true; // 檢查同一橫列的鄰居們,有沒有比他小?若有,他就無法幸運 for(int c=0;c<num_of_cols;c++){ if(matrix[i][c]<matrix[i][j]) lucky = false; } // 檢查同一直行的鄰居們,有沒有比他大?若有,他就無法幸運 for(int r=0;r<num_of_rows;r++){ if(matrix[r][j]>matrix[i][j]) lucky = false; } // 若他依然幸運,就把它加到答案裡 if(lucky) ret.push_back(matrix[i][j]); } } return ret; ``` <font color="darkgreen"> 【學生練習題】</font> > - [ ] [Leetcode 1582: 特別的數字 ](https://leetcode.com/problems/special-positions-in-a-binary-matrix/) > > 題目給一個名為 mat 的二維 vector > 請問總共有幾個「特別的格子」呢? > 格子被認為「特別」的條件是,他這格是1,且和他同一行同一列的其他格子全都是0 > > 圖中的藍色是特別的格子 > ![image](https://hackmd.io/_uploads/BJiCFPcwa.png) ![image](https://hackmd.io/_uploads/Bkpy5wqDp.png) > 左邊這個矩陣有 1 個特別格子,右邊這個矩陣有 3 個特別格子 <font color="darkgreen"> 【學生練習題】</font> > - [ ] [Leetcode 2352: 相同的行列 ](https://leetcode.com/problems/equal-row-and-column-pairs/) > > 題目給一個名為 grid 的二維 vector > 請問有幾對(行,列)是一樣的數字呢 > > 以下的例子,第二列和第一行都是 [2, 7, 7] > 總共一對 > ![image](https://hackmd.io/_uploads/BJ5eswqDa.png) > > 以下的例子,第零列和第零行都是 [3, 1, 2, 2] > 還有第二列和第二行都是 [2, 4, 2, 2] > 還有第三列和第二行都是 [2, 4, 2, 2] > 總共三對 > ![image](https://hackmd.io/_uploads/BkuMsDqvT.png) ## <font color='darkblue'>一維和二維的轉換</font> <font color='darkorange'>【例題】</font> > 班上有 `k` 位學生,座號分別是 `[0, 1, ..., k]` > 他們一起到了電腦教室 > 電腦教室總共有 m 排,每一排有 n 個座位 > 所以座號為 `[0, 1, ..., n-1]` 的這 n 個人要坐在第 0 排 > 座號為 `[n, n+1, ..., 2n-1]` 的這 n 個人要坐在第 1 排 > 以此類推 > 請依這個規則建立一個二維的 vector 把大家放到正確的座位上 > 但若 m*n 個座位不夠坐,或是坐不滿,就回傳空的二維 vector 這是 Leetcode 上的題目,連結是 [Leetcode 2022](https://leetcode.com/problems/convert-1d-array-into-2d-array/) 依據上述的規則 我們可以歸納出座號為 `i` 的人會被分配到第 `i/n` 排的第 `i%n` 個座位 ```cpp= // 如果學生人數不等於 m*n,就回傳空的二維 vector if(original.size()!=m*n) return vector<vector<int>>(); // 宣告一個 m*n 的 vector 來存答案 vector<vector<int>> ret(m, vector<int>(n)); for(int i=0;i<original.size();i++){ ret[i/n][i%n] = original[i]; } return ret; ``` <font color="darkgreen"> 【學生練習題】</font> > - [ ] [Leetcode 566: 重塑矩陣 ](https://leetcode.com/problems/reshape-the-matrix/) > > 學生們正依照例題的規則,照著座號坐在一間教室裡 > 他們現在被用一個名為 mat 的二維 vector 來表示 > 現在他們要換到一間新的教室 > 新教室總共有 r 排,每排有 c 個座位 > 大家依然要照座號坐,也就是第一排是`[0, 1, ..., c-1]`... > 請依照這個規則建立一個二維的 vector 把大家放到新教室的座位上 > 但若學生的數量並非 r*c,就請回傳原本的 mat 提示:可以先用一個一維的 vector 來暫存 mat 裡的數字 再用例題的方法把一維的 vector 變成 r*c 的二維 vector <font color="darkgreen"> 【學生練習題】</font> > - [ ] [Leetcode 1260: 往右 k 格 ](https://leetcode.com/problems/shift-2d-grid/) > > 學生們正依照例題的規則,照著座號坐在電腦教室裡 > 他們現在被用一個名為 grid 的二維 vector 來表示 > 今天考完了期中考,考卷由同學互相改 > 每個人的考卷都要"向右傳" k 次 > 每次向右傳,`grid[i][j]` 要被移到 `grid[i][j+1]` > `grid[i][n-1]` 也就是那一排的最後一位,要移到 `[i+1][0]` 也就是下一排的第一位 > `grid[m-1][n-1]` 也就是最後一排最後一位,要移到 `grid[0][0]` 也就是第一排第一位 > 請依照這個規則建立一個二維的 vector 把考卷移到正確的批改人手上 提示:老師有點瘋狂,k可能會是很大的數字,有可能比全班人數還要大 能不能利用數學運算,設計一個公式來讓每個人都直接算出他該把考卷交給誰呢? ## <font color='darkblue'>矩陣的旋轉</font> <font color='darkorange'>【例題】</font> > 班上的同學們依照自己的喜好坐在座位上 > 教室裡的座位剛好是個正方形(行數和列數一樣多) > > 今天老師把黑板移到了教室的右側 > 大家若是想要保持原本對於座位和黑板的相對位置的喜好 > 就必須隨著黑板的移動 > 把所有人的位置也都順時針旋轉90度 > 像是這樣: > ![image](https://hackmd.io/_uploads/SJ0on5cw6.png) > > 請在原本的矩陣 matrix 上幫大家完成旋轉 這是 Leetcode 上的題目,連結是 [Leetcode 48](https://leetcode.com/problems/rotate-image/) 我們可以先歸納出每個坐在`(r, c)`位置的人,他的新座位該怎麼計算 ![image](https://hackmd.io/_uploads/Sk_zTcqPa.png) 假設正方形邊長是 `n` 我們發現,一個原本在`(r, c)`位置的人,他的新座位必然是`(c, n-1-r)` 接下來,請注意大家是在「原本的教室」直接換座位 而不是去新的空教室坐 ![image](https://hackmd.io/_uploads/SJG3pc5Da.png) 所以換座位的過程會像是這樣 * 小藍跑到小綠的座位,說,起來,該我坐了 * 小綠只好跑到小黃的座位,把小黃趕走 * 小黃只好跑到小橘的座位,把小橘趕走 * 小橘於是跑到小藍的舊座位坐了下來,至此這四人順利完成了交換 用程式表示就是 ```cpp= int hold = matrix[r4][c4]; // 小橘先起來遊蕩 matrix[r4][c4] = matrix[r3][c3]; // 小黃去小橘的舊座位 matrix[r3][c3] = matrix[r2][c2]; // 小綠去小黃的舊座位 matrix[r2][c2] = matrix[r1][c1]; // 小藍去小綠的舊座位 matrix[r1][c1] = hold; // 小橘也有空位可坐了,就是小藍的舊座位 ``` 再來要注意的是,剛才小藍已經做一次「首先起身趕走新座位的同學」的行為了 小綠小黃小橘因此被動地完成了交換 他們三位就不需要再進行一次「首先起身趕走新座位的同學」的行為了 依照這個規則 我們可以把教室切成四個區域 然後永遠只讓藍色那塊區域的人來負責當「首先起身趕走新座位的同學」的人 其他三個區塊的人都只要被動地被移動 ![image](https://hackmd.io/_uploads/SJlyxicwT.png) 寫成程式就是這樣 ```cpp= int n=matrix.size(); for(int i=0;i<n/2;i++){ // 只讓藍色區域的人擔任小藍 for(int j=0;j<(n+1)/2;j++){ // 只讓藍色區域的人擔任小藍 int r1=i, c1=j; // 小藍 int r2=c1, c2=n-1-r1; // 小綠 int r3=c2, c3=n-1-r2; // 小黃 int r4=c3, c4=n-1-r3; // 小橘 // 剛才介紹過的移動 int hold = matrix[r4][c4]; matrix[r4][c4]=matrix[r3][c3]; matrix[r3][c3]=matrix[r2][c2]; matrix[r2][c2]=matrix[r1][c1]; matrix[r1][c1]=hold; } } ``` 記得這是 [Leetcode 48](https://leetcode.com/problems/rotate-image/) 喔! <font color="darkgreen"> 【學生練習題】</font> > - [ ] [Leetcode 1886: 能轉成目標的樣子嗎? ](https://leetcode.com/problems/determine-whether-matrix-can-be-obtained-by-rotation/) > > 題目給兩個正方形矩陣,分別是 mat 和 target > 請問我們能夠藉由旋轉 mat 若干次,把它變成和 target 一模一樣嗎? ## <font color='darkblue'>螺旋走訪矩陣</font> <font color='darkorange'>【例題】</font> > 老師開始點同學回答問題 > 依照「螺旋」的順序來點人 > 先從左到右點第一橫列的同學,轉彎 > 再從前到後點最右直行的同學,轉彎...以此類推 > ![image](https://hackmd.io/_uploads/rkQ7zicDa.png) > > 請回傳一個一維 vector,依序儲存被點到的人 > 以上面的例子,就是 `[1,2,3,4,8,12,11,10,9,5,6,7]` 這是 Leetcode 上的題目,連結是 [Leetcode 54](https://leetcode.com/problems/spiral-matrix/) 我們可以觀察出一個規則: 每次點完一個橫列或直排,要轉彎的那一瞬間 剩下還沒點到的同學都是組成一個長方形 因此我們可以用四個變數 top, bottom, left, right 分別代表「還沒被點到的同學」是從第幾橫列到第幾橫列 又是從第幾直行到第幾直行 ![image](https://hackmd.io/_uploads/ByJ2Boqwa.png) 螺旋的第一步,是從左到右,在最前排 (top) 上點人 ```cpp= for(int i=left;i<=right;i++){ ans.push_back(matrix[top][i]); } top += 1; ``` ![image](https://hackmd.io/_uploads/rkZAHs9D6.png) 螺旋的第二步,是從前到後,在最右排 (right) 上點人 ```cpp= for(int i=top;i<=bottom;i++){ ans.push_back(matrix[i][right]); } right -= 1; ``` ![image](https://hackmd.io/_uploads/rk3NUicvp.png) 螺旋的第三步,是從右到左,在最後一排 (bottom) 上點人 ```cpp= for(int i=right;i>=left;i--){ ans.push_back(matrix[bottom][i]); } bottom -= 1; ``` ![image](https://hackmd.io/_uploads/rkA_8oqPT.png) 螺旋的第四步,是從後到前,在最左排 (left) 上點人 ```cpp= for(int i=bottom;i>=top;i--){ ans.push_back(matrix[i][left]); } left += 1; ``` ![image](https://hackmd.io/_uploads/HyNnIi5Pp.png) 接著再回到第一步驟,在剩餘的長方形的第一排上,由左至右點人... 直到全班都被點完為止(剩餘的長方形為空) 寫成程式就是下述的樣子: ```cpp= int m = matrix.size(), n = matrix[0].size(); vector<int> ans; int top=0, bottom=m-1, left=0, right=n-1; while(true){ // 無限迴圈 // 從左到右,最前排 for(int i=left;i<=right;i++){ ans.push_back(matrix[top][i]); } top += 1; if(top>bottom) break; // 長方形為空的話,就跳脫離開迴圈 // 從前到後,最右排 for(int i=top;i<=bottom;i++){ ans.push_back(matrix[i][right]); } right -= 1; if(left>right) break; // 長方形為空的話,就跳脫離開迴圈 // 從右到左,最後排 for(int i=right;i>=left;i--){ ans.push_back(matrix[bottom][i]); } bottom -= 1; if(top>bottom) break; // 長方形為空的話,就跳脫離開迴圈 // 從後到前,最左排 for(int i=bottom;i>=top;i--){ ans.push_back(matrix[i][left]); } left += 1; if(left>right) break; // 長方形為空的話,就跳脫離開迴圈 } return ans; ``` 記得這是 [Leetcode 54](https://leetcode.com/problems/spiral-matrix/) 喔! <font color="darkgreen"> 【學生練習題】</font> > - [ ] [Leetcode 59: 創造螺旋矩陣 ](https://leetcode.com/problems/spiral-matrix-ii/) > > 題目給一個整數 n,請造出一個 n\*n 的矩陣 > 裡面內容是把 1 至 n\*n 的所有整數依照螺旋位置排放 > > 例如 n=3 > ![image](https://hackmd.io/_uploads/rkrMuiqDa.png) ## <font color='darkblue'>斜線走訪矩陣</font> <font color='darkorange'>【例題】</font> > 老師開始點同學回答問題 > 依照「斜線」的順序來點人 > 請看圖例: > ![image](https://hackmd.io/_uploads/H1tmto9wp.png) > 被點到的順序是 [1,2,4,7,5,3,6,8,9] > > 題目給一個名為 mat 的矩陣 > 請算出大家被點到的順序並存在一維 vector 裡回傳 這是 Leetcode 上的題目,連結是 [Leetcode 498](https://leetcode.com/problems/diagonal-traverse/) 觀察1: 用`(r, c)`來表示格子的座標,我們發現 屬於同一條斜線上的格子,他們的`r+c` 都是相同的值 `r+c` 最小為`0`,最大為 `(n-1)+(m-1)` ![image](https://hackmd.io/_uploads/HkEcqj9va.png) 觀察2: 那些「左下到右上的斜線」,也就是`sum=r+c` 是偶數的那幾條線 `c` 從小走到大,從 `0` 開始或是從 `sum-(m-1)` 開始,看哪個大 ![image](https://hackmd.io/_uploads/ry3riscvp.png) 觀察3: 那些「右上到左下的斜線」,也就是`sum=r+c` 是奇數的那幾條線 `r` 從小走到大,從 `0` 開始或是從 `sum-(n-1)` 開始,看哪個大 ![image](https://hackmd.io/_uploads/rkGg2j5vT.png) 因此我們可以以 `sum` 為主角,從 `0` 到`(n-1)+(m-1)` 交錯地拜訪「左下右上」和「右上左下」的斜線 寫成程式就是下述的樣子: ```cpp= vector<int> ret; int m = mat.size(); int n = mat[0].size(); for(int sum = 0; sum<=n+m-2;sum++){ // 左下到右上的斜線 for(int c = max(0, sum-m+1);c<n;c++){ int r = sum-c; if(r<0) break; ret.push_back(mat[r][c]); } sum++; // 右上到左下的斜線 for(int r = max(0, sum-n+1);r<m;r++){ int c = sum-r; if(c<0) break; ret.push_back(mat[r][c]); } } return ret; ``` 記得這是 [Leetcode 498](https://leetcode.com/problems/diagonal-traverse/) 喔! <font color="darkgreen"> 【學生練習題】</font> > - [ ] [Leetcode 766: 常對角矩陣 ](https://leetcode.com/problems/toeplitz-matrix/) > > 題目給一個二維 vector 名為 matrix > 請問他的每條「左上-右下」的斜線,都是同一個數值嗎? > > 是 > ![image](https://hackmd.io/_uploads/S1bkCi9v6.png) > > 不是 > ![image](https://hackmd.io/_uploads/rJ9JCjcw6.png) 提示:我們只要檢查每個格子是否等於他的「右下角」就可以了 ## > 回目錄:[國立科學園區實驗中學C++程式語言自學講義](https://hackmd.io/s/B18yT_i5Z)