Top View of Binary Tree
Bạn nhận được một 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 ở 2*i+1 (bên trái) và 2*i+2 (bên phải), còn -1 biểu thị vị trí trống.
Nút gốc nằm ở cột 0; nút con bên trái nằm ở cột bên trái nút cha một cột, còn nút con bên phải nằm ở cột bên phải một cột. Khi nhìn từ trên xuống, bạn thấy nút nông nhất ở mỗi cột; nếu có nhiều nút cùng độ sâu, chọn nút xuất hiện đầu tiên trong cấp đó khi đi từ trái sang phải (nút có chỉ số nhỏ hơn). Trả về các giá trị nhìn thấy, từ cột ngoài cùng bên trái đến cột ngoài cùng bên phải.
Hàm
- rootinteger-array
- cây theo thứ tự heap, với -1 cho vị trí trống
- Trả vềinteger-array
- giá trị nhìn từ trên xuống trong mỗi cột, bắt đầu từ cột ngoài cùng bên trá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] ≤ 104, 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 nằm 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
14cấp.
Ví dụ
- Đầu vào
- root = [5, 2, 9, 4, 7, 1, 6]
- Đầu ra
- [4, 2, 5, 9, 6]
- Giải thích
- Từ trái sang phải, các cột chứa
4(cột -2),2(-1), nút gốc5(0),9(1) và6(2).7và1cũng nằm ở cột 0, nhưng nút gốc nằm phía trên chúng.
- Đầu vào
- root = [1, 2, -1, -1, 3, -1, -1, -1, -1, -1, 4]
- Đầu ra
- [2, 1, 4]
- Giải thích
2nằm ở cột -1. Con bên phải của nó,3tại chỉ số 4, đã quay lại cột 0, bên dưới nút gốc, nên bị ẩn.4tại chỉ số 10, con bên phải của3, nằm riêng ở cột 1.
- Đầu vào
- root = [6, -1, -1]
- Đầu ra
- [6]
- Giải thích
- Chỉ số 1 và 2 chứa
-1, nên6đứng một mình ở cột 0 và là toàn bộ khung nhìn.
Câu hỏi mở rộng
Với các đối tượng nút thay vì một mảng, không có chỉ số để phân định khi hòa. Bạn sẽ nêu quy tắc phân định thế nào, và code tìm kiếm theo chiều rộng của bạn có thay đổi không?
Gợi ý
Mở từng gợi ý một. Mỗi gợi ý tiết lộ thêm một chút.
Đánh số các cột: cột gốc là 0, mỗi bước sang trái trừ 1 và mỗi bước sang phải cộng 1. Từ trên cao, bạn có thể nhìn thấy nút nào trong một cột, và làm thế nào để phân biệt nút đó với các nút khác trong cột?
Nếu bạn duyệt các nút theo từng cấp độ, từ trái sang phải trong mỗi cấp độ, bạn sẽ gặp nút nào của một cột trước tiên?
Thực hiện tìm kiếm theo chiều rộng trên các cặp (chỉ số, cột), thêm nút con bên trái trước nút con bên phải. Chỉ lưu giá trị ở lần đầu tiên một cột xuất hiện, theo dõi cột nhỏ nhất và lớn nhất, rồi đọc các cột theo thứ tự khi kết thúc.
Lời giải
Mỗi cột hiển thị nút nông nhất trong cột, nếu bằng nhau thì chọn nút xuất hiện trước theo thứ tự từng tầng. Đây chính xác là thứ tự mà thuật toán tìm kiếm theo chiều rộng duyệt các nút, vì vậy nút đầu tiên thuật toán gặp trong một cột chính là đáp án. Cách duyệt theo chiều sâu cũng được, nhưng cần theo dõi độ sâu và cẩn thận xử lý trường hợp bằng nhau.
Tìm kiếm theo chiều sâu, nút nông nhất trong mỗi cột
Ý tưởng
Duyệt cây theo chiều sâu với cột và độ sâu của từng nút, đồng thời lưu độ sâu và giá trị của nút nông nhất đã tìm thấy cho mỗi cột.
Chỉ thay thế khi độ sâu nhỏ hơn hẳn. Trong hai nút có độ sâu bằng nhau, nút đứng trước theo thứ tự từng tầng sẽ được gặp trước khi duyệt cây nếu ta khám phá toàn bộ cây con bên trái trước cây con bên phải tương ứng, vì tại điểm hai đường đi tách nhau, ta đi sang trái. Nếu thay thế khi độ sâu bằng nhau, nút được gặp sau sẽ thắng: khi chỉ số 3 để trống và các nút ở chỉ số 19 và 23, cả hai đều nằm ở cột -2, độ sâu 4, và đáp án là nút ở chỉ số 19.
Các cột có nút tạo thành một dải liên tục, vì nút ở cột -3 có các nút tổ tiên ở cột -1 và -2, nên hãy theo dõi cột nhỏ nhất và lớn nhất. Mỗi nút được duyệt một lần, mất thời gian O(n), và độ sâu đệ quy khoảng h lời gọi.
Thuật toán
- Truy cập nút gốc với cột 0 và độ sâu 0. Trả về ngay nếu chỉ số nằm ngoài mảng hoặc chứa
-1. - Nếu cột chưa có nút nào hoặc độ sâu hiện tại nhỏ hơn độ sâu đã lưu, hãy lưu độ sâu và giá trị này.
- Truy cập
2i+1với cộtcol-1, sau đó truy cập2i+2với cộtcol+1, sâu thêm một cấp. - Trả về các giá trị đã lưu theo thứ tự từ cột nhỏ nhất đến cột lớn nhất.
def topView(root):
n = len(root)
best = {} # column -> (depth, value) of the shallowest node found so far
def visit(i, col, depth):
if i >= n or root[i] == -1:
return
# Replace only on a strictly smaller depth: on a tie, the node
# met first is the one further left, which comes first in level order.
if col not in best or depth < best[col][0]:
best[col] = (depth, root[i])
visit(2 * i + 1, col - 1, depth + 1)
visit(2 * i + 2, col + 1, depth + 1)
visit(0, 0, 0)
# The columns that hold a node form one unbroken range.
return [best[col][1] for col in range(min(best), max(best) + 1)]Duyệt theo chiều rộng, nút đầu tiên trong mỗi cột
Ý tưởng
Một hàng đợi lấy nút gốc, rồi nút con bên trái và sau đó là nút con bên phải của từng nút, sẽ đưa các nút ra theo thứ tự từng tầng: mọi nút ở độ sâu d đều được đưa ra trước bất kỳ nút nào ở độ sâu d+1, và các nút trong mỗi tầng được đưa ra từ trái sang phải. Đó là thứ tự ưu tiên của bài toán, vì vậy nút đầu tiên mà một cột nhận được là nút nhìn thấy từ phía trên, còn mọi nút tiếp theo đều bị bỏ qua.
Mỗi nút cần một lần tra cứu bản đồ, tổng cộng là O(n). Hàng đợi có thể chứa toàn bộ một tầng, tối đa khoảng một nửa số nút, nên độ phức tạp không gian là O(n).
Thuật toán
- Đưa cặp (chỉ số 0, cột 0) vào hàng đợi.
- Lấy một cặp (
i,col) ra. Nếu bản đồ chưa có mục chocol, lưuroot[i]vào đó. - Thêm (
2i+1,col-1), rồi (2i+2,col+1) cho mỗi nút con tồn tại. - Khi hàng đợi trống, trả về các giá trị của bản đồ theo thứ tự từ cột nhỏ nhất đến cột lớn nhất.
from collections import deque
def topView(root):
n = len(root)
top = {} # column -> value of the first node seen in that column
queue = deque([(0, 0)]) # (node index, column); the root is never empty
while queue:
i, col = queue.popleft()
# The queue hands out shallower nodes first, and left before right
# on one level, so the first node of a column is the one seen from above.
if col not in top:
top[col] = root[i]
left, right = 2 * i + 1, 2 * i + 2
if left < n and root[left] != -1:
queue.append((left, col - 1))
if right < n and root[right] != -1:
queue.append((right, col + 1))
# The columns that hold a node form one unbroken range.
return [top[col] for col in range(min(top), max(top) + 1)]
Lỗi thường gặp và trường hợp biên
- Giữ lại nút đầu tiên mà lượt duyệt theo chiều sâu tìm thấy. Nếu nút con bên trái của nút gốc có một nút con bên phải, rồi nút đó cũng có một nút con bên phải (cột 1, độ sâu 3), lượt duyệt sẽ gặp nút này trước nút con bên phải của nút gốc (cột 1, độ sâu 1).
- Đưa nút con bên phải vào hàng đợi trước: khi hòa, nút không đúng sẽ được chọn.
- Sắp xếp các cột dưới dạng văn bản:
"-1"được sắp xếp trước"-2". - Cho rằng cây con bên trái chiếm các cột âm. Trong ví dụ 2, nút
4nằm trong cây con bên trái của nút gốc và ở cột 1.
Câu hỏi thường gặp3
Góc nhìn từ trên xuống của cây nhị phân là gì?
Đánh số các cột bắt đầu từ gốc là 0, giảm 1 khi đi sang trái đến nút con bên trái và tăng 1 khi đi sang phải đến nút con bên phải. Hình chiếu từ trên xuống liệt kê nút gần gốc nhất ở mỗi cột, từ trái sang phải.
Độ phức tạp thời gian của hình chiếu từ trên xuống của cây nhị phân là bao nhiêu?
Tìm kiếm theo chiều rộng duyệt qua mỗi nút trong số n nút đúng một lần, với mỗi nút cần một lần tra cứu trong bản đồ mất O(1), nên thuật toán chạy trong thời gian O(n) nếu bạn theo dõi cột nhỏ nhất và lớn nhất. Dùng bản đồ có thứ tự cho các cột sẽ khiến độ phức tạp là O(n log w) với w cột.
Hình chiếu từ trên khác hình chiếu từ trái và hình chiếu từ dưới như thế nào?
Góc nhìn bên trái chọn nút đầu tiên ở mỗi mức. Góc nhìn từ trên chọn nút nông nhất ở mỗi cột. Góc nhìn từ dưới chọn nút sâu nhất trong mỗi cột, vì vậy tìm kiếm theo chiều rộng sẽ ghi đè từng cột thay vì giữ lại nút đầu tiê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 topView(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, 9, 6]