Spiral Matrix
Bạn được cung cấp một ma trận số nguyên có m hàng và n cột, dưới dạng danh sách các hàng. Hãy trả về tất cả giá trị của ma trận theo thứ tự xoắn ốc.
Bắt đầu từ góc trên bên trái, đi sang phải dọc theo hàng trên cùng, sau đó đi xuống theo cột bên phải, sang trái dọc theo hàng dưới cùng rồi đi lên theo cột bên trái. Tiếp tục đi vòng vào trong theo chiều kim đồng hồ cho đến khi đọc mỗi giá trị đúng một lần.
Hàm
- matrixinteger-2d-array
- lưới số nguyên, dưới dạng danh sách (list) các hàng có độ dài bằng nhau
- Trả vềinteger-array
- mọi giá trị của ma trận theo thứ tự xoắn ốc theo chiều kim đồng hồ, bắt đầu từ góc trên bên trái
Ràng buộc
1 ≤ m, n ≤ 80, trong đóm = matrix.lengthvàn = matrix[i].length- Mỗi hàng có cùng độ dài
n. -100 ≤ matrix[i][j] ≤ 100
Ví dụ
- Đầu vào
- matrix = [[1, 2, 3], [10, 11, 4], [9, 12, 5], [8, 7, 6]]
- Đầu ra
- [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
- Giải thích
- Các giá trị tăng dần theo đường xoắn ốc. Vòng ngoài hiển thị từ
1đến10, còn lớp bên trong là một cột duy nhất, đọc một lần:11, 12.
- Đầu vào
- matrix = [[7, 1, 5, 3], [2, 9, -4, 6], [8, 0, 4, -1]]
- Đầu ra
- [7, 1, 5, 3, 6, -1, 4, 0, 8, 2, 9, -4]
- Giải thích
- Vòng ngoài cho ta
7, 1, 5, 3, 6, -1, 4, 0, 8, 2. Phần còn lại là hàng đơn9, -4, đọc một lần từ trái sang phải.
- Đầu vào
- matrix = [[4], [1], [7]]
- Đầu ra
- [4, 1, 7]
- Giải thích
- Một cột được đọc từ trên xuống dưới. Không thể quay lại phía trên vì mọi giá trị đã được đọc.
Câu hỏi mở rộng
Bạn có thể trả về các giá trị theo thứ tự ngược chiều kim đồng hồ, bắt đầu từ góc trên bên trái rồi đi xuống cột bên trái trước không?
Gợi ý
Mở từng gợi ý một. Mỗi gợi ý tiết lộ thêm một chút.
Hãy xem một vòng hoàn chỉnh đọc những gì: hàng trên cùng, cột bên phải, hàng dưới cùng và cột bên trái. Sau vòng đó, ma trận còn lại những gì?
Sau một vòng, phần còn lại là một ma trận nhỏ hơn, ngắn hơn một hàng ở phía trên và phía dưới, đồng thời hẹp hơn một cột ở mỗi bên. Giữ bốn giới hạn,
top,bottom,leftvàright, rồi dịch chúng vào trong sau mỗi vòng. Hãy chú ý đến lớp cuối cùng: lớp đó có thể chỉ gồm một hàng hoặc một cột.Khi
top ≤ bottomvàleft ≤ right: đọc hàng trên cùng từleftđếnright, sau đó đọc cột bên phải từtop+1đếnbottom. Chỉ khitop < bottomvàleft < right, đọc hàng dưới cùng từright-1ngược vềleftvà cột bên trái từbottom-1lêntop+1. Sau đó, dịch cả bốn giới hạn vào trong một bước.
Lời giải
Vấn đề nằm ở việc theo dõi các phần tử. Bạn cần đọc mỗi góc một lần, còn lớp trong cùng có thể chỉ gồm một hàng hoặc một cột, khiến một vòng đi đầy đủ sẽ đọc lại các giá trị. Bạn có thể đi và rẽ phải mỗi khi bị chặn, hoặc lần lượt bóc từng lớp của ma trận bằng bốn giới hạn thu hẹp dần mà không cần thêm bộ nhớ.
Đi về phía trước và rẽ phải khi gặp chướng ngại vật
Ý tưởng
Một người đi bộ bắt đầu ở ô trên cùng bên trái, hướng sang phải và đọc từng ô mà mình đi qua. Nếu bước tiếp theo sẽ ra khỏi ma trận hoặc đến một ô đã đọc, người đó rẽ phải. Các cạnh ngăn người đó ở vòng đầu tiên, còn những ô đã đọc sẽ làm tường cho mọi vòng sau, vì vậy mỗi vòng đều nằm bên trong vòng trước.
Trong ví dụ đầu tiên, người đi bộ đọc các số từ 1 đến 10 dọc theo cạnh. Phía trên 10 là ô 1, đã được đọc, nên người đó rẽ sang ô 11, rồi đi xuống ô 12. Chạy vòng lặp đúng m × n lần là bạn không cần phát hiện điểm kết thúc. Độ phức tạp thời gian là O(m × n), còn lưới seen tốn thêm O(m × n) bộ nhớ.
Thuật toán
- Bắt đầu tại hàng
0, cột0, chỉ số hướngd = 0, vớidr = [0, 1, 0, -1],dc = [1, 0, -1, 0]và lướiseenban đầu toàn giá trị false. - Lặp lại
m × nlần: thêm giá trị hiện tại và đánh dấu ô đó là đã thấy. - Nếu ô tiếp theo nằm ngoài ma trận hoặc đã được thấy, hãy đổi hướng bằng
d = (d+1) % 4. - Di chuyển một bước theo hướng
d.
def spiralOrder(matrix):
rows, cols = len(matrix), len(matrix[0])
seen = [[False] * cols for _ in range(rows)]
# Directions in clockwise order: right, down, left, up.
dr = [0, 1, 0, -1]
dc = [1, 0, -1, 0]
r = c = d = 0
result = []
for _ in range(rows * cols):
result.append(matrix[r][c])
seen[r][c] = True
nr, nc = r + dr[d], c + dc[d]
# Blocked by the edge or by a cell already read: turn right.
if not (0 <= nr < rows and 0 <= nc < cols) or seen[nr][nc]:
d = (d + 1) % 4
nr, nc = r + dr[d], c + dc[d]
r, c = nr, nc
return resultBóc tách từng lớp với bốn giới hạn
Ý tưởng
Mô tả vòng hiện tại bằng các hàng từ top đến bottom và các cột từ left đến right. Mỗi cạnh của một vòng bắt đầu từ ô ngay sau điểm cuối của cạnh trước đó, vì vậy mỗi góc chỉ được đọc một lần. Sau đó, thu hẹp cả bốn giới hạn vào trong.
Vòng chỉ dày một hàng hoặc một cột sẽ làm gián đoạn lượt duyệt. Trong ví dụ thứ hai, vòng cuối cùng là hàng 9, -4: hàng trên cùng đọc cả hai giá trị, còn nếu đi ngược lại qua hàng dưới cùng thì giá trị 9 sẽ được đọc thêm lần nữa. Trong ví dụ thứ ba, nếu đi ngược lên cột đơn thì giá trị 1 sẽ được đọc lại. Vì vậy, chỉ duyệt hàng dưới cùng và cột bên trái khi top < bottom và left < right.
Mỗi giá trị được đọc một lần, nên độ phức tạp thời gian là O(m × n), còn bộ nhớ phụ cần dùng là bốn số nguyên.
Thuật toán
- Đặt
top = 0,bottom = m-1,left = 0,right = n-1. - Trong khi
top ≤ bottomvàleft ≤ right, đọc hàng trên cùng từleftđếnrightvà cột bên phải từtop+1đếnbottom. - Nếu
top < bottomvàleft < right, đọc hàng dưới cùng từright-1đếnleftvà cột bên trái từbottom-1đếntop+1. - Cộng một vào
topvàleft, trừ một khỏibottomvàright.
def spiralOrder(matrix):
top, bottom = 0, len(matrix) - 1
left, right = 0, len(matrix[0]) - 1
result = []
while top <= bottom and left <= right:
# Top row, left to right, then right column, top to bottom.
for c in range(left, right + 1):
result.append(matrix[top][c])
for r in range(top + 1, bottom + 1):
result.append(matrix[r][right])
# A layer one row or one column thick has no way back:
# walking back would read the same cells again.
if top < bottom and left < right:
# Bottom row, right to left, then left column, bottom to top.
for c in range(right - 1, left - 1, -1):
result.append(matrix[bottom][c])
for r in range(bottom - 1, top, -1):
result.append(matrix[r][left])
# Step in to the next layer.
top += 1
bottom -= 1
left += 1
right -= 1
return result
Lỗi thường gặp và trường hợp biên
- Bỏ qua bước kiểm tra
top < bottomvàleft < right. Khi đó, ví dụ thứ hai kết thúc bằng9, -4, 9và ví dụ thứ ba cho kết quả4, 1, 7, 1. - Duyệt từng cạnh từ ô đầu tiên đến ô cuối cùng của cạnh đó. Khi đó, mỗi góc được đọc hai lần.
- Lặp khi
top < bottom. Trong ma trận3 × 3, giá trị ở giữa không bao giờ được đọc. - Dùng
matrix.lengthcho cả hai giới hạn. Cách này xử lý được mọi đầu vào ma trận vuông nhưng không xử lý được ma trận3 × 4. - Trong R,
3:2cho kết quả3, 2thay vì một khoảng rỗng. Hãy thêm điều kiện bảo vệ hoặc dùngseq_len. Trong Lua và R, chỉ số bắt đầu từ 1.
Câu hỏi thường gặp3
Độ phức tạp thời gian và không gian của Ma trận xoắn ốc là bao nhiêu?
Độ phức tạp thời gian là O(m × n), vì mỗi giá trị được đọc một lần và đáp án chứa mọi giá trị. Bóc từng lớp với bốn giới hạn cần thêm O(1) bộ nhớ. Cách đi đổi hướng khi bị chặn cần một lưới ô đã đi qua kích thước O(m × n).
Làm thế nào để tránh đọc một giá trị hai lần khi duyệt theo hình xoắn ốc?
Bắt đầu mỗi cạnh cách điểm kết thúc của cạnh trước đó một ô, để mỗi góc chỉ thuộc về một cạnh. Trong lớp chỉ dày một hàng hoặc một cột, hãy bỏ qua hàng dưới cùng và cột bên trái.
Làm thế nào để điền vào ma trận theo thứ tự xoắn ốc thay vì đọc theo thứ tự đó?
Dùng cùng bốn giới hạn và các cạnh, nhưng thay vì đọc thì hãy ghi. Lưu một bộ đếm bắt đầu từ 1 vào mỗi ô khi bạn đi qua, mỗi lần tăng thêm một. Với ma trận n × n, bộ đếm kết thúc ở n².
Bài tập tương tự
Các bài tập dùng cùng ý tưởng. Giải hai hoặc ba bài là bạn sẽ nhớ được mẫu này.
def spiralOrder(matrix):
# Viết code ở đâyKiểm thử #1
Kiểm thử #2
Kiểm thử #3
Đối số
matrix = [[1, 2, 3], [10, 11, 4], [9, 12, 5], [8, 7, 6]]
Đầu ra mong đợi
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]