Cyclically Rotating a Grid (LeetCode 1914)
Đề bài
1914. Cyclically Rotating a Grid – Xoay vòng bảng số (LeetCode, độ khó Medium)
Cho một ma trận số nguyên grid kích thước $m \times n$, trong đó $m$ và $n$ đều là số chẵn, và một số nguyên $k$.
Ma trận được tạo thành từ nhiều lớp như hình dưới, mỗi màu là một lớp:
Một lần xoay vòng ma trận được thực hiện bằng cách xoay vòng từng lớp của ma trận. Để xoay vòng một lớp một lần, mỗi phần tử trong lớp sẽ chiếm chỗ của phần tử kề với nó theo hướng ngược chiều kim đồng hồ. Ví dụ một lần xoay:
| 40 | 10 |
| 30 | 20 |
| 10 | 20 |
| 40 | 30 |
Hãy trả về ma trận sau khi thực hiện $k$ lần xoay vòng.
Ràng buộc:
- $m =$
grid.length, $n =$grid[i].length - $2 \le m, n \le 50$
- Cả $m$ và $n$ đều là số chẵn.
- $1 \le$
grid[i][j]$\le 5000$ - $1 \le k \le 10^9$
Định dạng vào/ra để chạy thử trên máy: trên LeetCode các bạn chỉ cần viết hàm rotateGrid trong class Solution. Để chạy như một chương trình bình thường (và dùng khung chạy tay bên dưới), mình đặt thêm định dạng:
- Dữ liệu vào: dòng đầu gồm 3 số nguyên $m, n, k$ (số dòng, số cột, số lần xoay); $m$ dòng tiếp theo, mỗi dòng $n$ số nguyên là một hàng của
grid. - Kết quả: $m$ dòng, mỗi dòng $n$ số cách nhau một dấu cách – ma trận sau khi xoay.
Input: grid = [[40,10],[30,20]],
k = 1
Output: [[10,20],[40,30]]
---stdin---
2 2 1
40 10
30 20
---stdout---
10 20
40 30Input: grid = [[1,2,3,4],[5,6,7,8],
[9,10,11,12],[13,14,15,16]],
k = 2
Output: [[3,4,8,12],[2,11,10,16],
[1,7,6,15],[5,9,13,14]]
---stdin---
4 4 2
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
---stdout---
3 4 8 12
2 11 10 16
1 7 6 15
5 9 13 141914. Cyclically Rotating a Grid (LeetCode, Medium)
You are given an m x n integer matrix grid, where m and n are both even integers, and an integer k.
The matrix is composed of several layers, which is shown in the below image, where each color is its own layer: (image – see the colored figure in the Vietnamese tab)
A cyclic rotation of the matrix is done by cyclically rotating each layer in the matrix. To cyclically rotate a layer once, each element in the layer will take the place of the adjacent element in the counter-clockwise direction. An example rotation is shown below: (image)
Return the matrix after applying k cyclic rotations to it.
Example 1:
Input: grid = [[40,10],[30,20]], k = 1
Output: [[10,20],[40,30]]
Explanation: The figures above represent the grid at every state.
Example 2:
Input: grid = [[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]], k = 2
Output: [[3,4,8,12],[2,11,10,16],[1,7,6,15],[5,9,13,14]]
Explanation: The figures above represent the grid at every state.
Constraints:
m == grid.lengthn == grid[i].length2 <= m, n <= 50- Both
mandnare even integers. 1 <= grid[i][j] <= 50001 <= k <= 10^9
Lời mở đầu
Bài này mình được một bạn học viên hỏi nên sẵn tiện mình viết bài giải chi tiết để nhiều bạn được tham khảo luôn. Hy vọng mang đến được giá trị cho các bạn 😇.
Bấm vào nút bên dưới để nộp bài và được hệ thống CLUE OJ chấm tự động.
Nộp bài trên CLUE OJLưu ý nhỏ: khi nộp trên CLUE OJ các bạn dán toàn bộ code, gồm cả hàm main, vì đề trên CLUE OJ đọc dữ liệu vào và in kết quả đúng theo định dạng ở phần đề bài, giống như khi các bạn chạy thử trên máy hoặc ở trang Chạy code online của blog.
Giải thích ví dụ 2 cho bạn nào chưa hiểu đề: ma trận $4 \times 4$ có 2 lớp – lớp ngoài gồm 12 số, lớp trong gồm 4 số $6, 7, 11, 10$. Mỗi lần xoay, cả hai lớp cùng "trượt" một ô theo chiều ngược kim đồng hồ (cột trái đi xuống, hàng dưới sang phải, cột phải đi lên, hàng trên sang trái):
| 1 | 2 | 3 | 4 |
| 5 | 6 | 7 | 8 |
| 9 | 10 | 11 | 12 |
| 13 | 14 | 15 | 16 |
| 2 | 3 | 4 | 8 |
| 1 | 7 | 11 | 12 |
| 5 | 6 | 10 | 16 |
| 9 | 13 | 14 | 15 |
| 3 | 4 | 8 | 12 |
| 2 | 11 | 10 | 16 |
| 1 | 7 | 6 | 15 |
| 5 | 9 | 13 | 14 |
Lộ trình lời giải, bấm vào từng phần để nhảy tới:
Cách 1Xoay từng bước, làm đủ k lần – O(k·m·n)
Làm đúng như đề mô tả: với mỗi lớp, lặp $k$ lần, mỗi lần dịch cả viền đi một ô ngược chiều kim đồng hồ (giữ lại số ở góc trên-trái, kéo hàng trên sang trái, cột phải lên trên, hàng dưới sang phải, cột trái xuống dưới, rồi đặt số đã giữ vào ô ngay dưới góc trên-trái).
Source code để các bạn tham khảo nếu làm theo hướng này:
#include <bits/stdc++.h>
using namespace std;
class Solution {
public:
vector<vector<int>> rotateGrid(vector<vector<int>>& grid, int k) {
int n = (int) grid.size();
int m = (int) grid[0].size();
for (int u = 0, d = n - 1, l = 0, r = m - 1; u <= d && l <= r; u++, d--, l++, r--) {
for (int step = 0; step < k; step++) {
// xoay viền 1 bước ngược chiều kim đồng hồ
int keep = grid[u][l];
for (int j = l; j < r; j++) grid[u][j] = grid[u][j + 1];
for (int i = u; i < d; i++) grid[i][r] = grid[i + 1][r];
for (int j = r; j > l; j--) grid[d][j] = grid[d][j - 1];
for (int i = d; i > u + 1; i--) grid[i][l] = grid[i - 1][l];
grid[u + 1][l] = keep;
}
}
return grid;
}
};
int main() {
int n, m, k;
cin >> n >> m >> k;
vector<vector<int>> grid(n, vector<int>(m));
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++) cin >> grid[i][j];
Solution sol;
vector<vector<int>> res = sol.rotateGrid(grid, k);
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) cout << res[i][j] << (j + 1 < m ? " " : "\n");
}
return 0;
}
import sys
from typing import List
class Solution:
def rotateGrid(self, grid: List[List[int]], k: int) -> List[List[int]]:
n, m = len(grid), len(grid[0])
u, d, l, r = 0, n - 1, 0, m - 1
while u <= d and l <= r:
for _ in range(k):
# xoay viền 1 bước ngược chiều kim đồng hồ
keep = grid[u][l]
for j in range(l, r):
grid[u][j] = grid[u][j + 1]
for i in range(u, d):
grid[i][r] = grid[i + 1][r]
for j in range(r, l, -1):
grid[d][j] = grid[d][j - 1]
for i in range(d, u + 1, -1):
grid[i][l] = grid[i - 1][l]
grid[u + 1][l] = keep
u, d, l, r = u + 1, d - 1, l + 1, r - 1
return grid
def main():
data = sys.stdin.read().split()
n, m, k = int(data[0]), int(data[1]), int(data[2])
vals = list(map(int, data[3:3 + n * m]))
grid = [vals[i * m:(i + 1) * m] for i in range(n)]
res = Solution().rotateGrid(grid, k)
print("\n".join(" ".join(map(str, row)) for row in res))
if __name__ == "__main__":
main()
Cách 2Liệt kê viền theo chiều ngược kim đồng hồ + dịch k mod chu kỳ – O(m·n)
Phương pháp giải: Implementation (cài đặt).
Ta chỉ cần làm theo đề là ra. Tuy nhiên bạn sẽ rất dễ lâm vào tình cảnh "code rối" nếu như không suy xét cẩn thận trước khi code.
Ta đi theo từng lớp của bảng, vì bảng chia làm nhiều lớp, và mỗi lớp chỉ như là "viền" của một bảng con. Ta gọi viền của một lớp là phần bảng từ hàng $u$ đến hàng $d$, từ cột $l$ đến cột $r$. Viền lớn nhất là $u = 0, d = m - 1, l = 0, r = n - 1$ (chỉ số từ 0), viền tiếp theo là $u = 1, d = m - 2, l = 1, r = n - 2$, … Như vậy khi đã xét xong viền hiện tại, ta tăng $u$ lên 1, giảm $d$ đi 1, tăng $l$ lên 1 và giảm $r$ đi 1.
- Liệt kê tọa độ các ô trên viền theo chiều ngược kim đồng hồ vào một mảng tạm
a: cột trái từ trên xuống, hàng dưới từ trái sang (bỏ 2 góc), cột phải từ dưới lên, hàng trên từ phải sang (bỏ 2 góc). - Một lần xoay thì phần tử ở vị trí thứ $i$ trong
asẽ dời sang vị trí thứ $i + 1$ (vị trí cuối quay về vị trí 0). - Tuy $k$ rất lớn ($1 \le k \le 10^9$), nhưng việc dịch chuyển có tính chu kỳ: viền có $C$ phần tử thì sau $C$ lần dịch, các phần tử lại trở về như cũ. Do đó số lần dịch thật sự chỉ là $k \bmod C$.
- Vậy phần tử tại tọa độ
a[i]của bảng cũ sẽ được ghi vào tọa độa[(i + k mod C) mod C]của bảng kết quả.
Ví dụ lớp ngoài của bảng $4 \times 4$: a = (0,0), (1,0), (2,0), (3,0), (3,1), (3,2), (3,3), (2,3), (1,3), (0,3), (0,2), (0,1) – đúng 12 ô, giá trị theo thứ tự đó là $1, 5, 9, 13, 14, 15, 16, 12, 8, 4, 3, 2$. Với $k = 2$: số 1 ở vị trí 0 đi tới vị trí 2 là ô $(2, 0)$; số 3 ở vị trí 10 đi tới vị trí $12 \bmod 12 = 0$ là ô $(0, 0)$ – khớp với đáp án.
Các bạn nhập bảng rồi bấm Tiếp để xem từng lớp được liệt kê và dịch (số nhỏ ở góc ô là thứ tự trong mảng a):
Code dưới đây của mình đã AC với tư tưởng đã trình bày ở trên, anh em có thể tham khảo nhé:
#include <bits/stdc++.h>
using namespace std;
class Solution {
public:
vector<vector<int>> rotateGrid(vector<vector<int>>& grid, int k) {
int n = (int) grid.size();
int m = (int) grid[0].size();
vector<vector<int>> res;
res.resize(n);
for (int i = 0; i < n; i++) {
res[i].resize(m, 0);
}
for (int u = 0, d = n - 1, l = 0, r = m - 1; u <= d && l <= r; u++, d--, l++, r--) {
vector<pair<int, int>> a;
for (int i = u; i <= d; i++) {
a.push_back(make_pair(i, l));
}
for (int i = l + 1; i <= r - 1; i++) {
a.push_back(make_pair(d, i));
}
for (int i = d; i >= u; i--) {
a.push_back(make_pair(i, r));
}
for (int i = r - 1; i >= l + 1; i--) {
a.push_back(make_pair(u, i));
}
int sz = (int) a.size();
int cycle = k % sz;
for (int i = 0; i < sz; i++) {
int x = a[i].first;
int y = a[i].second;
int nextx = a[(i + cycle) % sz].first;
int nexty = a[(i + cycle) % sz].second;
res[nextx][nexty] = grid[x][y];
}
}
return res;
}
};
int main() {
int n, m, k;
cin >> n >> m >> k;
vector<vector<int>> grid(n, vector<int>(m));
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++) cin >> grid[i][j];
Solution sol;
vector<vector<int>> res = sol.rotateGrid(grid, k);
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) cout << res[i][j] << (j + 1 < m ? " " : "\n");
}
return 0;
}
import sys
from typing import List
class Solution:
def rotateGrid(self, grid: List[List[int]], k: int) -> List[List[int]]:
n, m = len(grid), len(grid[0])
res = [[0] * m for _ in range(n)]
u, d, l, r = 0, n - 1, 0, m - 1
while u <= d and l <= r:
a = []
for i in range(u, d + 1):
a.append((i, l))
for i in range(l + 1, r):
a.append((d, i))
for i in range(d, u - 1, -1):
a.append((i, r))
for i in range(r - 1, l, -1):
a.append((u, i))
sz = len(a)
cycle = k % sz
for i in range(sz):
x, y = a[i]
nx, ny = a[(i + cycle) % sz]
res[nx][ny] = grid[x][y]
u, d, l, r = u + 1, d - 1, l + 1, r - 1
return res
def main():
data = sys.stdin.read().split()
n, m, k = int(data[0]), int(data[1]), int(data[2])
vals = list(map(int, data[3:3 + n * m]))
grid = [vals[i * m:(i + 1) * m] for i in range(n)]
res = Solution().rotateGrid(grid, k)
print("\n".join(" ".join(map(str, row)) for row in res))
if __name__ == "__main__":
main()
Giải thích một số chỗ trong code:
- Trong code,
nlà số hàng vàmlà số cột (đặt tên theo thói quen của mình), các bạn đọc kỹ để khỏi nhầm với $m, n$ trong đề. - 4 vòng
forliệt kê viền: cột trái lấy đủ từ hàng $u$ tới $d$, cột phải cũng lấy đủ từ $d$ về $u$, còn hàng dưới và hàng trên chỉ lấy phần giữa (cột $l + 1$ tới $r - 1$) để không lấy trùng 4 góc. - Ghi kết quả sang bảng mới
reschứ không ghi đè lêngrid, nhờ vậy không lo "đè mất" số chưa kịp dời.
Khung chạy tay (để dễ theo dõi, giới hạn $m, n \le 6$):
Đánh giá độ phức tạp của cách làm này:
- Không gian: bảng kết quả
resvà mảng tạma– $O(m \cdot n)$. - Thời gian: mỗi ô của bảng được đưa vào
ađúng một lần và được ghi sangresđúng một lần, không phụ thuộc $k$ – $O(m \cdot n)$.
- Không biết học gì trước, học gì sau, học lan man nên kiến thức bị hổng.
- Bí bài không ai gỡ, ngồi cả buổi vẫn không ra rồi bỏ cuộc.
- Code chạy được nhưng không đủ điểm, không biết sai ở đâu, chậm ở đâu.
- Không có đề thi thử và người chấm, nên không biết mình đang ở mức nào.
- Dễ nản giữa chừng khi không có ai theo sát.
- Lộ trình 111 buổi video sắp sẵn từ dòng code đầu tiên đến đề HSG thật của các tỉnh.
- Thầy trực tiếp gỡ bí, chữa bài cho từng em qua nhóm Zalo riêng.
- Hệ thống chấm bài tự động 24/7 và thi thử HSG hằng tuần để biết chính xác mình đang ở đâu.
- Đóng học phí một lần, thầy kèm lâu dài: từ thi chuyên Tin đến HSG quốc gia và cả khi lên đại học.
Nhận xét
Đăng nhận xét