Vertical Order Traversal of a Binary Tree
Cây nhị phân được lưu trong mảng root theo thứ tự heap: nút gốc ở chỉ số 0, các nút con của chỉ số i nằm tại 2*i+1 (bên trái) và 2*i+2 (bên phải), -1 đánh dấu một vị trí trống, và mảng có thể kết thúc bằng các phần tử -1 thừa.
Nút gốc nằm ở cột 0; nút con bên trái nằm ở cột ngay bên trái nút cha, nút con bên phải nằm ở cột ngay bên phải. Trả về một danh sách cho mỗi cột, từ trái sang phải. Trong mỗi cột, liệt kê các nút từ trên xuống dưới, và sắp xếp các nút cùng cột và cùng độ sâu theo thứ tự giá trị tăng dần.
Hàm
- rootinteger-array
- cây theo thứ tự heap, với -1 cho vị trí trống
- Trả vềinteger-2d-array
- mỗi cột là một danh sách giá trị, theo thứ tự từ trái sang phải, lần lượt từ trên xuống dưới
Ràng buộc
1 ≤ root.length ≤ 32767- Mỗi
root[i]là-1hoặc một giá trị thỏa mãn0 ≤ root[i] ≤ 5000, và không có giá trị nào xuất hiện hai lần. root[0]không bao giờ là-1, vì vậy cây có ít nhất một nút.- Mảng có thể kết thúc bằng các phần tử
-1thừa sau nút cuối cùng. - Cả hai nút con của một vị trí trống cũng đều trống, và cây có tối đa
14tầng.
Ví dụ
- Đầu vào
- root = [5, 2, 9, 4, 7, 1, 6]
- Đầu ra
- [[4], [2], [5, 1, 7], [9], [6]]
- Giải thích
- Cột 0 chứa nút gốc
5, sau đó là7và1ở thấp hơn hai cấp. Hai nút này có cùng cột và độ sâu, vì vậy1đứng trước7, dù7nằm xa hơn về bên trái.
- Đầu vào
- root = [1, 2, 3, -1, 4, -1, -1, -1, -1, 5]
- Đầu ra
- [[2, 5], [1, 4], [3]]
- Giải thích
- Cột -1 chứa
2ở độ sâu 1 và5ở độ sâu 3 (nút con bên trái của4). Cột 0 chứa nút gốc1và4ở chỉ số 4. Cột 1 chỉ chứa3.
- Đầu vào
- root = [6, -1, -1]
- Đầu ra
- [[6]]
- Giải thích
6không có phần tử con, nên đáp án có một cột,[6].
Câu hỏi mở rộng
Mỗi giá trị đều không vượt quá 5000. Bạn có thể tận dụng điều đó để bỏ phép sắp xếp so sánh và hoàn thành trong thời gian O(n + 5000) không?
Gợi ý
Mở từng gợi ý một. Mỗi gợi ý tiết lộ thêm một chút.
Mỗi nút có một cột, một độ sâu và một giá trị. Nếu có tất cả các bộ ba đó trong một danh sách, bạn sẽ sắp xếp chúng theo thứ tự nào trong câu trả lời?
Tìm kiếm theo chiều rộng đã duyệt các nút từ trên xuống dưới. Phần nào trong thứ tự vẫn còn thiếu khi nhiều nút cùng nằm trên một cột và một tầng?
Xử lý cây theo từng cấp độ. Sắp xếp từng cấp độ theo cột, rồi theo giá trị, thêm từng giá trị vào danh sách của cột tương ứng, cuối cùng đọc các danh sách từ cột nhỏ nhất đến cột lớn nhất.
Lời giải
Mỗi nút có ba khóa: column, depth và value. Đáp án là các nút được sắp xếp theo những khóa đó, mỗi cột gồm một danh sách. Khi column và depth bằng nhau, hãy sắp xếp theo value, không theo thứ tự trái sang phải mà thuật toán duyệt gặp các nút. Vì vậy, bạn phải sắp xếp tất cả các nút cùng lúc hoặc sắp xếp từng cấp trong quá trình tìm kiếm theo chiều rộng.
Ghi lại mọi nút, sau đó sắp xếp
Ý tưởng
Ghé thăm mỗi nút một lần, theo thứ tự bất kỳ, và ghi lại bộ ba (cột, độ sâu, giá trị). Mỗi bước đi đến nút con làm tăng độ sâu thêm 1 và dịch cột sang trái hoặc phải 1 đơn vị. Sau đó, sắp xếp các bộ ba theo cột, độ sâu và giá trị. Các nút trong mỗi cột giờ nằm cạnh nhau theo đúng thứ tự yêu cầu. Duyệt danh sách đã sắp xếp và bắt đầu một danh sách con mới mỗi khi cột thay đổi.
Trong ví dụ 1, các bộ ba của cột 0 được sắp xếp thành (0, 0, 5), (0, 2, 1), (0, 2, 7). Thao tác sắp xếp có độ phức tạp O(n log n), chi phối thao tác duyệt có độ phức tạp O(n), còn các bộ ba cần O(n) bộ nhớ.
Thuật toán
- Đẩy (chỉ số 0, cột 0, độ sâu 0) vào một ngăn xếp.
- Lấy một nút ra, ghi lại (cột, độ sâu, giá trị), rồi đẩy từng nút con thực vào với cột
col-1hoặccol+1và độ sâudepth+1. - Khi ngăn xếp trống, sắp xếp các bộ ba theo cột, rồi độ sâu, rồi giá trị.
- Duyệt qua chúng, bắt đầu một danh sách mới mỗi khi cột thay đổi.
def verticalTraversal(root):
n = len(root)
entries = [] # (column, depth, value) for every node
stack = [(0, 0, 0)] # (node index, column, depth); the root is never empty
while stack:
i, col, depth = stack.pop()
entries.append((col, depth, root[i]))
for child, child_col in ((2 * i + 1, col - 1), (2 * i + 2, col + 1)):
if child < n and root[child] != -1:
stack.append((child, child_col, depth + 1))
# Column, then depth, then value: exactly the order of the answer.
entries.sort()
result = []
for k, (col, depth, value) in enumerate(entries):
if k == 0 or col != entries[k - 1][0]:
result.append([]) # a new column starts
result[-1].append(value)
return resultSắp xếp bên trong từng cấp độ, lần lượt từ cấp độ này sang cấp độ khác
Ý tưởng
Tìm kiếm theo chiều rộng xử lý từng mức một. Khi xử lý, nếu mỗi nút thêm giá trị của nó vào danh sách của cột tương ứng, thì các danh sách đã có thứ tự từ trên xuống dưới, nên không cần sắp xếp theo độ sâu. Các trường hợp bằng nhau chỉ xảy ra trong cùng một mức, vì vậy hãy sắp xếp từng mức theo cột, rồi đến giá trị, trước khi xử lý mức đó.
Trong ví dụ 1, mức 2 có các giá trị 4, 7, 1, 6 ở các cột -2, 0, 0, 2 và sau khi sắp xếp sẽ thành 4, 1, 7, 6, nên cột 0 trở thành [5, 1, 7]. Các cột có nút tạo thành một khoảng liên tục, nên chỉ cần theo dõi cột nhỏ nhất và lớn nhất.
Sắp xếp một mức gồm m nút tốn O(m log m). Trường hợp xấu nhất vẫn là O(n log n), vì mức cuối cùng của một cây đầy có khoảng một nửa số nút. Độ phức tạp không gian là O(n).
Thuật toán
- Bắt đầu với một tầng chứa (chỉ số 0, cột 0).
- Sắp xếp tầng theo cột, sau đó theo giá trị.
- Theo thứ tự đó, thêm từng giá trị vào danh sách của cột tương ứng, rồi thu thập các nút con thực sự của nó, với các cột
col-1vàcol+1, vào tầng tiếp theo. - Lặp lại cho đến khi tầng trống, đồng thời theo dõi cột nhỏ nhất và lớn nhất.
- Trả về các danh sách từ cột nhỏ nhất đến cột lớn nhất.
def verticalTraversal(root):
n = len(root)
columns = {} # column -> its values, top to bottom
level = [(0, 0)] # (node index, column) of every node on the current level
while level:
# Every node here has the same depth: order them by column, then value.
level.sort(key=lambda node: (node[1], root[node[0]]))
next_level = []
for i, col in level:
columns.setdefault(col, []).append(root[i])
for child, child_col in ((2 * i + 1, col - 1), (2 * i + 2, col + 1)):
if child < n and root[child] != -1:
next_level.append((child, child_col))
level = next_level
# The columns that hold a node form one unbroken range.
return [columns[col] for col in range(min(columns), max(columns) + 1)]
Lỗi thường gặp và trường hợp biên
- Giữ các nút hòa theo thứ tự từ trái sang phải. Tìm kiếm theo chiều rộng không sắp xếp theo từng tầng sẽ cho
[5, 7, 1]ở cột 0 của ví dụ 1, chứ không phải[5, 1, 7]. - Thêm giá trị trong khi duyệt theo chiều sâu mà không sắp xếp. Trong
[1, 2, 3, -1, 4, -1, -1, -1, -1, -1, 5], cách duyệt ưu tiên trái sẽ thêm5(độ sâu 3) vào cột 1 trước3(độ sâu 1). - Sắp xếp theo cột và giá trị nhưng không theo độ sâu. Khi đó, cột 0 của ví dụ 1 sẽ có thứ tự
[1, 5, 7]. - Dùng chuỗi làm khóa cho các cột. Khi sắp xếp dưới dạng văn bản,
"-1"đứng trước"-2", khiến các cột bên trái cột 0 không đúng thứ tự.
Câu hỏi thường gặp3
Độ phức tạp thời gian của phép duyệt theo thứ tự dọc của cây nhị phân là bao nhiêu?
Sắp xếp tất cả các nút theo cột, độ sâu và giá trị tốn O(n log n) và chi phối lượt duyệt O(n). Sắp xếp theo từng tầng có cùng độ phức tạp trong trường hợp xấu nhất, vì tầng cuối cùng của một cây đầy đủ chứa khoảng một nửa số nút. Độ phức tạp không gian là O(n).
Có thể duyệt theo thứ tự dọc mà không cần sắp xếp không?
Chỉ khi các nút hòa nhau được xếp theo thứ tự từ trái sang phải. Khi đó, tìm kiếm theo chiều rộng, thêm từng giá trị vào danh sách của cột tương ứng, sẽ cho đáp án trong O(n). Nếu các nút hòa nhau được xếp theo giá trị, bạn cần sắp xếp, nhưng chỉ trong từng tầng, vì chỉ các nút ở cùng một tầng mới có thể hòa nhau.
Thứ tự duyệt theo chiều dọc liên quan thế nào đến góc nhìn từ trên và góc nhìn từ dưới?
Cả ba cách đều nhóm các nút theo cùng số cột. Hình chiếu từ trên xuống giữ lại nút nông nhất ở mỗi cột, còn hình chiếu từ dưới lên giữ lại nút sâu nhất. Phép duyệt theo thứ tự dọc giữ lại mọi nút và sắp xếp theo giá trị khi có nhiều nút cùng tầng, còn các hình chiếu chọn nút theo vị trí.
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 verticalTraversal(root):
# Viết code ở đâyKiểm thử #1
Kiểm thử #2
Kiểm thử #3
Đối số
root = [5, 2, 9, 4, 7, 1, 6]
Đầu ra mong đợi
[[4], [2], [5, 1, 7], [9], [6]]