Tuyển sinh 🎓 Mình nhận đào tạo lập trình giải thuật thi đấu: thi vào lớp 10 chuyên tin · thi học sinh giỏi tin học cấp tỉnh/thành · thi vào đội tuyển quốc gia · Olympic tin học sinh viên / ICPC · phỏng vấn thuật toán. Bấm vào đây để xem thông tin khóa học → Xem khóa học

Xoay ma trận

Xoay ma trận
Nguồn: CLUE OJ – Chương 11: Mảng 2 chiều Độ khó: Dễ Mảng 2 chiều Biến đổi chỉ số Chuyển vị

Đề bài

Cho ma trận $n \times n$ gồm các phần tử nguyên, hãy xoay ma trận 90 độ theo chiều kim đồng hồ.

Input:

  • Dòng đầu tiên gồm số nguyên $n$.
  • $n$ dòng tiếp theo, mỗi dòng gồm $n$ số nguyên, thể hiện ma trận.

Output: in ra ma trận đã cho sau khi xoay.

Điều kiện: $1 \le n \le 50$; $1 \le A_i \le 100$.

Ví dụ – input
3
1 2 3
4 5 6
7 8 9
Ví dụ – output
7 4 1
8 5 2
9 6 3

Giải thích ví dụ: xoay 90° theo chiều kim đồng hồ thì hàng trên cùng 1 2 3 "ngã" sang bên phải thành cột cuối cùng (đọc từ trên xuống: 1, 2, 3); hàng dưới cùng 7 8 9 thành cột đầu tiên. Đọc lại theo từng hàng ta được 7 4 1 / 8 5 2 / 9 6 3.

Lời mở đầu

Xoay ma trận là bài kinh điển để luyện khả năng biến đổi chỉ số trên mảng 2 chiều: ô $(i, j)$ đi đâu sau khi xoay? Nghĩ ra được công thức thì code chỉ vài dòng; nghĩ sai thì rất dễ xoay ngược chiều mà ví dụ nhìn qua vẫn "na ná". Mình sẽ trình bày 3 cách: dùng mảng phụ theo công thức, in trực tiếp không cần mảng phụ, và xoay tại chỗ bằng "chuyển vị + lật" – một mẹo rất hay gặp trong phỏng vấn.

Dành cho học viên · Ưu tiênNộp bài trên hệ thống CLUE OJ

Link này chỉ học viên đã tham gia tổ chức mới vào được. Học viên nên ưu tiên nộp ở đây vì sẽ được chấm nhanh hơn.

Nộp bài trên CLUE OJ

Lộ trình lời giải, bấm vào từng phần để nhảy tới:

Bộ testBộ test chính thức có gì?

Bộ test có 100 test, đề không chia subtask. Mình chia theo độ lớn thành 3 nhóm:

TestnGiá trị phần tử
1 – 303 … 101 … 100
31 – 805 … 501 … 100
81 – 10050 … 501 … 100
  • Không có test nào với $n = 1$ hay $n = 2$, $n$ nhỏ nhất là 3 (chính là test ví dụ ở test 1). Các nhóm 1, 2 có cả $n$ chẵn và lẻ – với $n$ lẻ có một ô ở chính giữa đứng yên khi xoay.
  • Mọi phần tử nằm trong $[1, 100]$ đúng như đề, phần tử lặp lại rất nhiều nên phải in đúng vị trí, không "ăn may" được.
  • Ma trận lớn nhất chỉ $50 \times 50 = 2500$ số nên mọi cách $O(n^2)$ đều chạy tức thì.
MứcĐiều kiệnTestCách 1Cách 2Cách 3
1$n \le 10$1 – 30✔✔✔
2$5 \le n \le 50$ ngẫu nhiên31 – 80✔✔✔
3$n = 50$81 – 100✔✔✔

Ý tưởngÔ (i, j) đi đâu sau khi xoay?

Đánh số hàng, cột từ 1. Khi xoay 90° theo chiều kim đồng hồ:

  • Hàng 1 (trên cùng) trở thành cột $n$ (bên phải cùng), hàng 2 thành cột $n - 1$, …, hàng $i$ thành cột $n + 1 - i$.
  • Trong một hàng, phần tử bên trái ($j$ nhỏ) sau khi "ngã" sang phải sẽ nằm trên: phần tử ở cột $j$ thành phần tử ở hàng $j$.
Công thức xoay 90° theo chiều kim đồng hồ
$$b[j][n + 1 - i] = a[i][j]$$ hay viết ngược lại theo ô kết quả: $b[i][j] = a[n + 1 - j][i]$ – tức là hàng $i$ của kết quả chính là cột $i$ của $a$, đọc từ dưới lên.

Kiểm tra với ví dụ: $a[1][1] = 1$ đi tới $b[1][3]$ (góc trên phải), $a[3][1] = 7$ đi tới $b[1][1]$ (góc trên trái). Đúng như output 7 4 1 ở hàng đầu.

Em nhập ma trận (các hàng cách nhau dấu phẩy) rồi bấm Tiếp để xem từng hàng "ngã" thành cột. Mỗi ô có ghi toạ độ gốc bé ở góc và tô màu theo hàng gốc:

Ví dụ nhanh:

Cách 1Mảng phụ theo công thức

Dùng thêm mảng b, duyệt mọi ô của a và đặt vào đúng chỗ b[j][n + 1 - i], cuối cùng in b.

C++▶ Chạy tay từng dòng codeCách 1: đặt từng ô a[i][j] vào b[j][n + 1 − i] rồi in b.
Dữ liệu mẫu:
Lúc xoay: ô viền vàng trên a là ô đang xét, ô nháy vàng trên b là chỗ vừa đặt nó vào, ô xanh là các ô của b đã có giá trị.
  1. Vì đánh số từ 1 nên cột đích là n + 1 - i. Nếu đánh số từ 0 (như bản Python) thì công thức thành b[j][n - 1 - i] = a[i][j].
  2. Mảng khai báo [55][55] toàn cục là đủ cho $n \le 50$ và tự được khởi tạo bằng 0.
Không gian$O(n^2)$ (thêm mảng b)
Thời gian$O(n^2)$

Kết quả chạy thật: C++ đúng 100/100 test, test chậm nhất 0.006 giây. Python đúng 100/100 test, test chậm nhất 0.026 giây.

Cách 2In trực tiếp, không cần mảng phụ

Đề chỉ yêu cầu in ra ma trận sau khi xoay, nên ta không cần thật sự tạo ra nó. Theo công thức $b[i][j] = a[n + 1 - j][i]$: hàng $i$ của kết quả là cột $i$ của a đọc từ dưới lên. Vậy chỉ cần 2 vòng lặp: $i$ chạy theo cột của a, $j$ chạy từ $n$ về 1 theo hàng.

C++▶ Chạy tay từng dòng codeCách 2: in cột i của a từ dưới lên thành hàng i của kết quả, không cần mảng phụ.
Dữ liệu mẫu:
Cột tím là cột i đang in; ô viền vàng là ô vừa in; ô xanh phía dưới là các ô của cột đã in xong.
Không gian$O(n^2)$ (chỉ mảng a)
Thời gian$O(n^2)$

Kết quả chạy thật: C++ đúng 100/100 test, test chậm nhất 0.003 giây. Python đúng 100/100 test, test chậm nhất 0.024 giây.

Lỗi hay gặp: xoay ngược chiều
Nếu viết vòng ngoài j từ $n$ về 1 và in a[i][j] với i từ 1 đến $n$ (in các cột từ phải sang trái, mỗi cột đọc từ trên xuống) thì ma trận bị xoay ngược chiều kim đồng hồ: ví dụ ra 3 6 9 / 2 5 8 / 1 4 7. Mình đã nộp thử phiên bản sai này lên bộ test: 0/100 test đúng – không ăn được test nào, kể cả test ví dụ. Vì vậy luôn kiểm tra lại công thức bằng 1–2 ô ở góc trước khi code nhé.
for(int j = n; j >= 1; j--){          // SAI: các cột từ phải sang trái
    for(int i = 1; i <= n; i++) cout << a[i][j] << " ";   // mỗi cột đọc từ trên xuống
    cout << "\n";
}

Cách 3Xoay tại chỗ: chuyển vị rồi lật

Nếu đề bắt phải biến đổi ngay trên mảng a (không được dùng mảng phụ – ví dụ để dùng tiếp cho các thao tác khác), ta tách phép xoay thành 2 phép "lật" đơn giản:

Xoay 90° = chuyển vị + lật ngang
Bước 1 – chuyển vị: đổi chỗ $a[i][j] \leftrightarrow a[j][i]$ với mọi $i < j$ (lật qua đường chéo chính). Sau bước này $a'[i][j] = a[j][i]$.
Bước 2 – đảo ngược từng hàng (lật trái ↔ phải): $a''[i][j]$ $= a'[i][n + 1 - j]$ $= a[n + 1 - j][i]$ – đúng bằng công thức xoay!

Ở bước 1, chỉ duyệt $j > i$ (nửa trên đường chéo). Nếu duyệt mọi cặp $(i, j)$ thì mỗi cặp bị đổi 2 lần và ma trận quay về như cũ – một lỗi rất hay gặp. Em bấm Tiếp để xem 2 bước lật:

Ví dụ nhanh:
C++▶ Chạy tay từng dòng codeCách 3: chuyển vị rồi đảo ngược từng hàng, xoay ngay trên mảng a.
Dữ liệu mẫu:
Bước 1: 2 ô nháy vàng là cặp đối xứng qua đường chéo vừa đổi chỗ. Bước 2: cả hàng nháy vàng là hàng vừa bị đảo ngược.
Không gian$O(n^2)$, không cần mảng phụ
Thời gian$O(n^2)$

Kết quả chạy thật: C++ đúng 100/100 test, test chậm nhất 0.004 giây. Python đúng 100/100 test, test chậm nhất 0.024 giây.

Mẹo nhớ: chuyển vị rồi đảo từng hàng → xoay theo chiều kim đồng hồ; chuyển vị rồi đảo thứ tự các hàng (lật trên ↔ dưới) → xoay ngược chiều kim đồng hồ. Xoay 180° = đảo thứ tự hàng rồi đảo từng hàng.

Ghi chú của mình: mình đã chạy cả 3 cách (C++ và Python) trên toàn bộ 100 test chính thức của CLUE OJ: tất cả đều đúng 100/100, test chậm nhất dưới 0.03 giây. Phiên bản xoay ngược chiều thì sai cả 100 test. Cách 2 ngắn nhất để nộp bài; Cách 3 đáng nhớ vì nó xoay ngay trên mảng, hay dùng khi phải xoay nhiều lần liên tiếp (như các bài game 2048, xếp hình).
Rất vui khi anh em nào đã đọc đến những dòng cuối cùng này. Anh em đọc chắc cũng mỏi mắt luôn chứ 😅 vậy anh em nghĩ người viết ra hết đống ở trên còn thế nào nữa 🤪. Vậy nên đừng tiếc gì cho mình một lời bình luận hoặc chia sẻ bài viết để động viên mình nè. Chúc anh em học tốt và gặt hái nhiều thành công 😇

Nhận xét