題解:從二維數(shù)組坐標(biāo)映射到C++/Python實(shí)現(xiàn))
記錄一下自己做 OpenJudge NOI 1.8 第 11 題“圖像旋轉(zhuǎn)”的過(guò)程。說(shuō)實(shí)話(huà)這道題在信息學(xué)奧賽一本通里算是數(shù)組章節(jié)的??碗y度不大但每次比賽或者練習(xí)里遇到“矩陣變換”類(lèi)題目總有人在這里翻車(chē)——要么行列搞反要么下標(biāo)換錯(cuò)要么輸出格式出了毛病。尤其是第一次接觸二維數(shù)組和坐標(biāo)映射的同學(xué)非常值得停下來(lái)好好吃透它。這道題的核心任務(wù)很直接給定一個(gè) n 行 m 列的矩陣把它順時(shí)針旋轉(zhuǎn) 90 度后輸出。聽(tīng)起來(lái)像是“把圖轉(zhuǎn)個(gè)方向”這么簡(jiǎn)單可一旦落到代碼里涉及的就是二維數(shù)組的下標(biāo)映射、行列互換、循環(huán)邊界控制這些基本功。我今天把從讀題到推導(dǎo)、從 C 實(shí)現(xiàn)到 Python 實(shí)現(xiàn)、從常見(jiàn)錯(cuò)誤到逆時(shí)針/180度變式完整梳理一遍給你一份能直接“抄作業(yè)”的方案。1. 題目拆解URL、題面和它背后的考點(diǎn)1.1 題面原文與輸入輸出格式題目本身非常簡(jiǎn)潔題面大意是輸入第一行是兩個(gè)正整數(shù) n 和 m表示矩陣有 n 行 m 列。接下來(lái) n 行每行 m 個(gè)整數(shù)組成一個(gè)矩陣。要求將這個(gè)矩陣順時(shí)針旋轉(zhuǎn) 90 度后輸出。輸出是一個(gè) m 行 n 列的矩陣每個(gè)數(shù)字之間用一個(gè)空格隔開(kāi)。舉個(gè)例子輸入3 3 1 2 3 4 5 6 7 8 9期望輸出7 4 1 8 5 2 9 6 3這里 n3m3旋轉(zhuǎn)前后都是 3 行 3 列很多人就會(huì)忽略“行和列會(huì)互換”這個(gè)關(guān)鍵點(diǎn)。如果換成 n2、m3 的不規(guī)則矩陣輸出就會(huì)變成 3 行 2 列很多人第一次寫(xiě)就會(huì)在這里被卡住。1.2 為什么一道“旋轉(zhuǎn)題”值得單獨(dú)拎出來(lái)寫(xiě)光看題目你可能覺(jué)得簡(jiǎn)單但它在競(jìng)賽里的地位不低。一方面它是信息學(xué)奧賽一本通里二維數(shù)組這一章的經(jīng)典過(guò)渡題前面學(xué)完一維數(shù)組、二維數(shù)組的基本讀寫(xiě)突然來(lái)一個(gè)“矩陣變換”考察的恰恰是你有沒(méi)有真正理解二維數(shù)組的存儲(chǔ)邏輯而不只是會(huì)照著格式寫(xiě)循環(huán)。另一方面“矩陣旋轉(zhuǎn)”是很多算法題的雛形。圖像處理里面做圖片旋轉(zhuǎn)、游戲開(kāi)發(fā)里做地圖旋轉(zhuǎn)、機(jī)器學(xué)習(xí)里做數(shù)據(jù)增強(qiáng)底層都會(huì)用到這種坐標(biāo)映射的思想。你把這道題的推導(dǎo)過(guò)程弄明白了后面遇到“旋轉(zhuǎn)圖像”“翻轉(zhuǎn)矩陣”“轉(zhuǎn)置矩陣”這些題目基本就是套模板。所以我建議初學(xué)者不要滿(mǎn)足于 AC而是要把公式推導(dǎo)和循環(huán)邊界徹底搞懂。2. 從“看圖旋轉(zhuǎn)”到“下標(biāo)換位”公式是怎么來(lái)的2.1 先拿 3x3 矩陣找感覺(jué)拿到這類(lèi)題第一步不是寫(xiě)代碼而是在草稿紙上畫(huà)圖。我習(xí)慣先在紙上寫(xiě)一個(gè) 3 行 3 列的矩陣再在旁邊畫(huà)一個(gè)旋轉(zhuǎn)后的結(jié)果然后用箭頭標(biāo)出每個(gè)元素從舊位置到新位置的路徑。以輸入矩陣 A 為例A[0][0]1 A[0][1]2 A[0][2]3 A[1][0]4 A[1][1]5 A[1][2]6 A[2][0]7 A[2][1]8 A[2][2]9順時(shí)針旋轉(zhuǎn) 90 度后得到B[0][0]7 B[0][1]4 B[0][2]1 B[1][0]8 B[1][1]5 B[1][2]2 B[2][0]9 B[2][1]6 B[2][2]3仔細(xì)觀察原矩陣的第一行1 2 3旋轉(zhuǎn)后變成了 B 的最后一列而且順序是從上到下1, 2, 3變成 B[0][2]、B[1][2]、B[2][2]。原矩陣的最后一列3 6 9旋轉(zhuǎn)后變成了 B 的第一行但順序變了7 4 1。你會(huì)發(fā)現(xiàn)旋轉(zhuǎn)前后每個(gè)元素的移動(dòng)其實(shí)很有規(guī)律只是“行變成列、列反向變成行”。這個(gè)規(guī)律不要用眼睛硬記要用坐標(biāo)算。2.2 坐標(biāo)映射公式推導(dǎo)我們要做的是建立 A[i][j] 和 B[x][y] 之間的對(duì)應(yīng)關(guān)系其中 A 是 n 行 m 列B 是 m 行 n 列。思路是這樣把矩陣旋轉(zhuǎn)想象成把一張紙順時(shí)針轉(zhuǎn)動(dòng)。原來(lái)在第 i 行、第 j 列的元素旋轉(zhuǎn)之后到了新矩陣的哪一行哪一列從上面的例子能看出兩個(gè)規(guī)律第一原矩陣的第 j 列經(jīng)過(guò)順時(shí)針旋轉(zhuǎn) 90 度后變成了新矩陣的第 i 行嗎不對(duì)我這里有更好的歸納方式。讓我們逐個(gè)觀察坐標(biāo)變化還是用剛才的 3x3 例子A[0][0]1原來(lái)在左上角旋轉(zhuǎn)后到右上角即 B[0][2]A[0][1]2原來(lái)在第一行中間旋轉(zhuǎn)后到 B[1][2]A[0][2]3原來(lái)在右上角旋轉(zhuǎn)后到右下角即 B[2][2]A[1][0]4原來(lái)在中間行左邊旋轉(zhuǎn)后到 B[0][1]A[2][0]7原來(lái)在左下角旋轉(zhuǎn)后到左上角即 B[0][0]把這幾組對(duì)應(yīng)關(guān)系列出來(lái)原位置 A[i][j]新位置 B[x][y]A[0][0]B[0][2]A[0][1]B[1][2]A[0][2]B[2][2]A[1][0]B[0][1]A[2][0]B[0][0]先看行坐標(biāo)A[0][0] 到了 B 的第 0 行A[1][0] 到了 B 的第 0 行A[2][0] 到了 B 的第 0 行。原矩陣列下標(biāo) j0 的元素都到了 B 的第 0 行再看 j1 的 A[0][1]到了 B 的第 1 行j2 的 A[0][2]到了 B 的第 2 行。所以新矩陣的行坐標(biāo)就是原矩陣的列坐標(biāo)x j。再看列坐標(biāo)原矩陣行下標(biāo) i0 的元素A[0][0] 到 B 的第 2 列A[0][1] 到 B 的第 2 列A[0][2] 到 B 的第 2 列。也就是說(shuō)i0 的時(shí)候y n-1-i 3-1-0 2。i1 的時(shí)候A[1][0] 到 B 的第 1 列y 3-1-1 1。i2 的時(shí)候A[2][0] 到 B 的第 0 列y 3-1-2 0。于是公式就出來(lái)了A[i][j] 旋轉(zhuǎn)后應(yīng)該放到 B[j][n-1-i]也就是B[j][n-1-i] A[i][j]這個(gè)公式對(duì)任意 n 行 m 列的矩陣都成立。旋轉(zhuǎn)后的 B 有 m 行 n 列因?yàn)?j 的取值范圍是 0 到 m-1n-1-i 的取值范圍是 0 到 n-1。我一開(kāi)始學(xué)的時(shí)候死活記不住這個(gè)式子后來(lái)發(fā)現(xiàn)一個(gè)更直觀的記憶方式順時(shí)針旋轉(zhuǎn) 90 度其實(shí)就是原矩陣的“列”變成新矩陣的“行”原矩陣的“行”倒過(guò)來(lái)變成新矩陣的“列”。原數(shù)組的第 i 行第 j 列元素會(huì)跑到新數(shù)組的第 j 行第 n-1-i 列。你看行坐標(biāo)直接交換成列坐標(biāo)列坐標(biāo)變成倒過(guò)來(lái)的行坐標(biāo)。2.3 從公式到循環(huán)兩種遍歷方向公式推導(dǎo)出來(lái)之后寫(xiě)循環(huán)就有兩種思路。思路一遍歷原矩陣把每個(gè)元素放到新位置外層循環(huán)遍歷 i原矩陣的行內(nèi)層循環(huán)遍歷 j原矩陣的列每次把 A[i][j] 賦給 B[j][n-1-i]。這樣寫(xiě)的好處是不用關(guān)心目標(biāo)矩陣的遍歷順序只要保證每個(gè)元素都被“搬”到正確的位置即可。思路二遍歷新矩陣反推原坐標(biāo)外層循環(huán)遍歷 i新矩陣的行0 到 m-1內(nèi)層循環(huán)遍歷 j新矩陣的列0 到 n-1通過(guò)逆公式 B[i][j] A[n-1-j][i] 來(lái)取原矩陣中的值。這種寫(xiě)法不需要額外的矩陣來(lái)暫存直接按新矩陣的行列順序去原矩陣?yán)锶?duì)應(yīng)值輸出就行。兩種思路都能 AC但我個(gè)人更推薦第一種因?yàn)樗N近“模擬旋轉(zhuǎn)過(guò)程”邏輯上不容易出錯(cuò)調(diào)試的時(shí)候也方便。第二種適合在空間受限或者你想省一個(gè)二維數(shù)組的時(shí)候用。3. 代碼實(shí)現(xiàn)C 和 Python 各來(lái)一份3.1 C 標(biāo)準(zhǔn)寫(xiě)法先上我最常用的 C 寫(xiě)法。定義兩個(gè)二維數(shù)組一個(gè)存原矩陣一個(gè)存旋轉(zhuǎn)后的矩陣。需要注意數(shù)組大小要開(kāi)夠免得下標(biāo)越界。#include iostream using namespace std; const int MAXN 105; int a[MAXN][MAXN], b[MAXN][MAXN]; int main() { int n, m; cin n m; for (int i 0; i n; i) { for (int j 0; j m; j) { cin a[i][j]; } } // 順時(shí)針旋轉(zhuǎn)90度 for (int i 0; i n; i) { for (int j 0; j m; j) { b[j][n - 1 - i] a[i][j]; } } // 輸出旋轉(zhuǎn)后的矩陣注意行列數(shù)是 m 行 n 列 for (int i 0; i m; i) { for (int j 0; j n; j) { if (j 0) cout ; cout b[i][j]; } cout endl; } return 0; }這里有幾個(gè)細(xì)節(jié)想特別提醒第一MAXN最好根據(jù)題目數(shù)據(jù)范圍來(lái)定。如果題目沒(méi)說(shuō)范圍通常開(kāi) 105 或 1005 足夠。有些題 n、m 可以到 1000這時(shí)你開(kāi) 105 就會(huì)越界程序可能運(yùn)行出錯(cuò)或者輸出亂碼。建議看題面實(shí)在不行就開(kāi)vectorvectorint動(dòng)態(tài)分配最穩(wěn)妥。第二輸出的時(shí)候要控制空格。我習(xí)慣的判斷方式是if (j 0) cout ;這樣每行末尾不會(huì)多一個(gè)空格。OpenJudge 的判題系統(tǒng)通常不 Care 行末空格但有些 OJ 會(huì)嚴(yán)格要求所以最好還是養(yǎng)成嚴(yán)謹(jǐn)?shù)母袷搅?xí)慣。第三旋轉(zhuǎn)后矩陣是 m 行 n 列而不是 n 行 m 列。這個(gè)太容易寫(xiě)錯(cuò)了尤其是當(dāng) n 不等于 m 的時(shí)候一旦搞反輸出矩陣的維數(shù)就不對(duì)看起來(lái)就像是“整個(gè)程序只輸出了前幾行”很多人會(huì)誤以為旋轉(zhuǎn)公式錯(cuò)了其實(shí)只是輸出循環(huán)邊界寫(xiě)錯(cuò)。3.2 Python 實(shí)現(xiàn)與注意點(diǎn)如果你用 Python 刷題那么可以用列表推導(dǎo)式來(lái)簡(jiǎn)化代碼。但要注意Python 里創(chuàng)建二維列表有一個(gè)經(jīng)典陷阱不要用[[0] * n] * m這種方式因?yàn)檫@樣創(chuàng)建出來(lái)的每一行是同一個(gè)對(duì)象的引用改一個(gè)其他行也會(huì)跟著變。正確做法是用列表推導(dǎo)式[[0] * n for _ in range(m)]。n, m map(int, input().split()) a [list(map(int, input().split())) for _ in range(n)] # 初始化旋轉(zhuǎn)后的矩陣m 行 n 列 b [[0] * n for _ in range(m)] for i in range(n): for j in range(m): b[j][n - 1 - i] a[i][j] for row in b: print( .join(map(str, row)))這段代碼和 C 版本邏輯完全一致。Python 寫(xiě)起來(lái)更簡(jiǎn)潔但有一個(gè)地方要特別留意input().split()默認(rèn)按照空白字符分割能處理多個(gè)連續(xù)空格這點(diǎn)不用擔(dān)心。但如果輸入數(shù)據(jù)特別大比如 1000x1000 的矩陣用 Python 的for _ in range(n)讀入可能會(huì)有點(diǎn)慢這時(shí)候可以考慮用sys.stdin.read()一次性讀入再切分。另外b [[0] * n for _ in range(m)]這里的行列不要寫(xiě)反。很多人習(xí)慣寫(xiě)[[0] * m for _ in range(n)]結(jié)果后面賦值和輸出時(shí)矩陣形狀不對(duì)程序會(huì)直接 IndexError。3.3 一維數(shù)組的另類(lèi)思路有些題目限制了數(shù)組維數(shù)或者你想省點(diǎn)空間也可以用一維數(shù)組模擬二維矩陣。原理很簡(jiǎn)單假設(shè)原矩陣有 n 行 m 列那么元素 A[i][j] 在一維數(shù)組中的位置是i * m j。旋轉(zhuǎn)后矩陣有 m 行 n 列元素 B[x][y] 在一維數(shù)組中的位置是x * n y。核心公式變?yōu)閎[j * n (n - 1 - i)] a[i * m j];如果理解了這個(gè)映射你會(huì)發(fā)現(xiàn)“一維數(shù)組還是二維數(shù)組”只是存儲(chǔ)方式不同坐標(biāo)轉(zhuǎn)換的思維完全一樣。這在某些內(nèi)存極小的競(jìng)賽環(huán)境里很有用日常刷題用二維數(shù)組就足夠了。4. 那些年踩過(guò)的坑常見(jiàn)錯(cuò)誤與調(diào)試技巧4.1 最容易翻車(chē)的三個(gè)點(diǎn)我在帶學(xué)弟學(xué)妹刷題的時(shí)候發(fā)現(xiàn)這道題的錯(cuò)誤主要集中在三個(gè)地方。第一個(gè)是行列搞反。原矩陣 n 行 m 列旋轉(zhuǎn)后是 m 行 n 列。很多人在輸出的時(shí)候慣性寫(xiě)成 n 行 m 列結(jié)果題目用例是 3x3 時(shí)沒(méi)問(wèn)題一換成 2x3 或者 3x2 就立刻出錯(cuò)。我的建議是拿到題目先圈出“輸出的是 m 行 n 列”這個(gè)關(guān)鍵信息寫(xiě)循環(huán)之前先在注釋里標(biāo)明變量含義。第二個(gè)是下標(biāo)公式用錯(cuò)。最常見(jiàn)的是把B[j][n-1-i]寫(xiě)成B[n-1-i][j]或者寫(xiě)成B[j][i]。前者是把“行坐標(biāo)變成列坐標(biāo)”這個(gè)步驟漏掉了后者是完全沒(méi)處理反向。每次寫(xiě)完拿一個(gè) 3 行 4 列的矩陣在草稿紙上手動(dòng)走一遍基本能當(dāng)場(chǎng)發(fā)現(xiàn)。第三個(gè)是二維數(shù)組開(kāi)小了。有些同學(xué)看到題目樣例只有 3 行 3 列就開(kāi)a[5][5]結(jié)果數(shù)據(jù)范圍其實(shí)是 1 到 100。運(yùn)行時(shí)下標(biāo)越界在 C 里不一定報(bào)錯(cuò)可能只是悄悄覆蓋了內(nèi)存里的其他數(shù)據(jù)最后輸出一串神秘?cái)?shù)字。建議養(yǎng)成習(xí)慣數(shù)組至少比最大值大 5或者干脆開(kāi)到 505、1005。4.2 用“手動(dòng)走查”代替盲目調(diào)試如果你提交后答案是錯(cuò)的先別急著改代碼。我的調(diào)試方法很簡(jiǎn)單把題目的樣例輸入復(fù)制出來(lái)在草稿紙上畫(huà)一個(gè) 2 行 3 列的小矩陣然后用手算一遍旋轉(zhuǎn)結(jié)果再拿你的程序輸出對(duì)比。比如輸入2 3 1 2 3 4 5 6人工旋轉(zhuǎn)一下順時(shí)針 90 度之后變成4 1 5 2 6 3如果你程序的輸出是4 1 5 2 6 3那就說(shuō)明主體邏輯沒(méi)問(wèn)題可能問(wèn)題出在別的測(cè)試數(shù)據(jù)上。如果輸出完全對(duì)不上就看第一個(gè)錯(cuò)位的元素是從哪個(gè)坐標(biāo)來(lái)的倒推它被賦值到了哪里。這個(gè)方法雖然原始但往往比盯著代碼發(fā)呆高效得多。還有一個(gè)很實(shí)用的小技巧在關(guān)鍵循環(huán)里臨時(shí)加一行cout i i j j - x j y n-1-i endl;把每個(gè)元素的去向打印出來(lái)。這樣你能直觀看到坐標(biāo)映射是否符合預(yù)期。調(diào)試完記得刪掉這些輸出。4.3 關(guān)于輸入格式的補(bǔ)充說(shuō)明OpenJudge 的題目有時(shí)候會(huì)在數(shù)據(jù)中夾雜空行尤其是從文件復(fù)制過(guò)來(lái)的樣例數(shù)據(jù)。用cin n m這種流式讀取會(huì)自動(dòng)跳過(guò)空白字符所以一般不用擔(dān)心空行問(wèn)題。但如果你用scanf(%d, n)配合判定 EOF也要注意處理可能多出來(lái)的換行符。Python 這邊如果用input().split()遇到空行會(huì)報(bào)錯(cuò)可以用sys.stdin.read().split()把所有數(shù)字一次性讀出來(lái)再切片分配對(duì)刁鉆數(shù)據(jù)會(huì)更穩(wěn)。5. 舉一反三逆時(shí)針、180度與轉(zhuǎn)置5.1 逆時(shí)針旋轉(zhuǎn) 90 度既然會(huì)了順時(shí)針逆時(shí)針其實(shí)是一個(gè)道理。原矩陣 A[n][m]逆時(shí)針旋轉(zhuǎn) 90 度后結(jié)果仍然是 m 行 n 列但坐標(biāo)映射變成了C[m-1-j][i] A[i][j]拿 3x3 驗(yàn)證一下A[0][2]3 應(yīng)該到結(jié)果矩陣的第 0 行第 0 列。m3j2所以 m-1-j0i0C[0][0]3和手動(dòng)逆時(shí)針旋轉(zhuǎn)的結(jié)果一致。如果你不想記兩個(gè)公式也可以投機(jī)取巧逆時(shí)針旋轉(zhuǎn) 90 度等價(jià)于順時(shí)針旋轉(zhuǎn) 270 度也就是先順時(shí)針轉(zhuǎn) 90 度再順時(shí)針轉(zhuǎn) 180 度。但實(shí)際寫(xiě)代碼時(shí)直接用逆時(shí)針公式最省事。5.2 旋轉(zhuǎn) 180 度旋轉(zhuǎn) 180 度更簡(jiǎn)單行列都不互換只是行和列都反向D[n-1-i][m-1-j] A[i][j]結(jié)果矩陣仍然是 n 行 m 列。這個(gè)變換在很多題目里也會(huì)出現(xiàn)比如判斷矩陣是否中心對(duì)稱(chēng)或者做圖像翻轉(zhuǎn)。它的規(guī)律很好記上下顛倒之后再左右顛倒。5.3 從“旋轉(zhuǎn)”到“轉(zhuǎn)置”的思維升級(jí)接下來(lái)我想多說(shuō)一個(gè)進(jìn)階點(diǎn)轉(zhuǎn)置。轉(zhuǎn)置不是旋轉(zhuǎn)它只是把矩陣的行和列互換但不做反向。公式是E[j][i] A[i][j]順時(shí)針旋轉(zhuǎn) 90 度和“先轉(zhuǎn)置再水平翻轉(zhuǎn)”是等價(jià)的。換句話(huà)說(shuō)轉(zhuǎn)置A[i][j] - A[j][i]水平翻轉(zhuǎn)A[i][j] - A[i][m-1-j]先轉(zhuǎn)置再水平翻轉(zhuǎn)得到順時(shí)針旋轉(zhuǎn) 90 度的結(jié)果為什么說(shuō)這個(gè)思維升級(jí)很重要因?yàn)檎鎸?shí)的圖像旋轉(zhuǎn)算法里往往不是暴力地把每個(gè)像素搬運(yùn)一遍而是先做轉(zhuǎn)置再做鏡像翻轉(zhuǎn)這樣可以借助緩存局部性提高效率。競(jìng)賽雖然不考這個(gè)但你要是能理解這層關(guān)系以后看更復(fù)雜的矩陣題會(huì)輕松很多。我把這三種常用變換整理成了一張表方便你復(fù)習(xí)變換類(lèi)型坐標(biāo)映射公式結(jié)果矩陣尺寸典型應(yīng)用順時(shí)針 90 度B[j][n-1-i] A[i][j]m 行 n 列圖像旋轉(zhuǎn)、地圖旋轉(zhuǎn)逆時(shí)針 90 度C[m-1-j][i] A[i][j]m 行 n 列圖像鏡像處理旋轉(zhuǎn) 180 度D[n-1-i][m-1-j] A[i][j]n 行 m 列中心對(duì)稱(chēng)判斷轉(zhuǎn)置E[j][i] A[i][j]m 行 n 列矩陣運(yùn)算基礎(chǔ)5.4 更深一步原地旋轉(zhuǎn)的思路如果你遇到的是“要求原地旋轉(zhuǎn)”的題目也就是不允許使用額外數(shù)組那就復(fù)雜一些了。以 n x n 的方陣為例順時(shí)針旋轉(zhuǎn) 90 度可以通過(guò)四元素輪轉(zhuǎn)完成。思路是把矩陣分成若干層從外到內(nèi)每層再分成若干組每組四個(gè)元素循環(huán)互換位置。這一層一層的邏輯很有意思但超出了這道題的范疇我建議把基礎(chǔ)的搬運(yùn)式寫(xiě)法吃透之后再挑戰(zhàn)原地旋轉(zhuǎn)。不過(guò)有一點(diǎn)可以提醒方陣的原地旋轉(zhuǎn)和長(zhǎng)方陣的旋轉(zhuǎn)處理方式不太一樣。方陣轉(zhuǎn)完還是 n 行 n 列可以通過(guò)多次交換實(shí)現(xiàn)長(zhǎng)方陣原地旋轉(zhuǎn)通常需要借助轉(zhuǎn)置加翻轉(zhuǎn)的組合操作或者干脆申請(qǐng)新數(shù)組競(jìng)賽里一般不刁難你去原地旋轉(zhuǎn)不等長(zhǎng)矩陣所以考試時(shí)優(yōu)先考慮新開(kāi)一個(gè)數(shù)組的寫(xiě)法。6. 刷題路上的心得體會(huì)最后以一個(gè)過(guò)來(lái)人的身份說(shuō)幾句。像“圖像旋轉(zhuǎn)”這種題在信息學(xué)奧賽的題單里屬于那種“會(huì)者不難、難者不會(huì)”的過(guò)渡題。它沒(méi)有高深的算法也不涉及復(fù)雜的數(shù)據(jù)結(jié)構(gòu)但它考察的是你能不能把一個(gè)直觀的幾何操作轉(zhuǎn)化成精確的數(shù)學(xué)表達(dá)。很多同學(xué)學(xué)到后面卡殼不是栽在動(dòng)態(tài)規(guī)劃和圖論上而是栽在這種最基礎(chǔ)的坐標(biāo)變換和邊界條件上。我自己的習(xí)慣是每刷一道矩陣題就在筆記里畫(huà)一個(gè) 2 行 3 列的小例子然后手動(dòng)把旋轉(zhuǎn)、翻轉(zhuǎn)、轉(zhuǎn)置的結(jié)果都寫(xiě)一遍再把坐標(biāo)變化規(guī)律記在公式旁邊。這樣積累幾道題之后你會(huì)發(fā)現(xiàn)所有矩陣變換題都是同一套思維模型找到原坐標(biāo)和新坐標(biāo)之間的映射關(guān)系剩下的就是兩個(gè)循環(huán)的事。這道題本身 AC 只算第一步真正有價(jià)值的是把順時(shí)針旋轉(zhuǎn)公式、逆時(shí)針旋轉(zhuǎn)公式、轉(zhuǎn)置公式放在一起對(duì)比記憶。等你以后做到圖像處理相關(guān)的題目再回頭看今天這幾十行代碼會(huì)覺(jué)得這段基礎(chǔ)打得特別值。