Bottom View of Binary Tree
Mảng root chứa một cây nhị phân: nút gốc ở chỉ số 0, các nút con của chỉ số i ở 2i+1 (bên trái) và 2i+2 (bên phải), -1 biểu thị vị trí trống, và có thể có thêm các phần tử -1 ở cuối.
Nút gốc nằm ở cột 0; đi sang trái thì lùi một cột, đi sang phải thì tiến một cột. Nhìn từ bên dưới, mỗi cột hiển thị nút sâu nhất, nếu các nút ở cùng độ sâu thì chọn nút cuối cùng khi đọc từ trái sang phải ở mức đó (nút có chỉ số lớn hơn). Trả về các giá trị bạn nhìn thấy, bắt đầu từ cột ngoài cùng bên trá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ừ bên dưới trong từng 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 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ó nhiều nhất
14cấp.
Ví dụ
- Đầu vào
- root = [5, 2, 9, 4, 7, 1, 6]
- Đầu ra
- [4, 2, 1, 9, 6]
- Giải thích
- Cột 0 chứa
5và ở cấp thấp hơn hai bậc là7(chỉ số 4) và1(chỉ số 5).1xuất hiện sau trên cùng cấp, nên nó được hiển thị. Các cột -2, -1, 1 và 2 chứa4,2,9và6.
- Đầu vào
- root = [1, 2, 3, -1, 4, -1, -1, -1, -1, 5]
- Đầu ra
- [5, 4, 3]
- Giải thích
- Cột -1 chứa
2và, ở thấp hơn hai tầng, có5(chỉ số 9), nên bạn thấy5. Ở cột 0,4nằm bên dưới nút gốc. Cột 1 chỉ chứa3.
- Đầu vào
- root = [8, -1, -1, -1]
- Đầu ra
- [8]
- Giải thích
- Mọi vị trí sau nút gốc đều là
-1, vì vậy8là nút duy nhất và cột 0 là cột duy nhất.
Câu hỏi mở rộng
Nếu cần trả về mọi nút đồng hạng ở mức sâu nhất của một cột, theo thứ tự từ trái sang phải, thì phiên bản duyệt theo chiều rộng sẽ thay đổi như thế nào? Cột 0 của ví dụ 1 sẽ cho kết quả gì?
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: nút gốc ở cột 0, mỗi bước sang trái trừ 1 và mỗi bước sang phải cộng 1. Khi nhìn từ bên dưới, bạn thấy nút nào trong một cột, và điều gì xảy ra nếu hai nút trong cột đó nằm cùng một mức?
Thăm các nút theo từng cấp, từ trái sang phải trong mỗi cấp, rồi ghi giá trị của từng nút vào cột tương ứng, thay thế nội dung đã có. Cuối cùng, mỗi cột còn lại gì?
Dùng một hàng đợi gồm các cặp (index, column) và thêm nút con bên trái vào hàng đợi trước nút con bên phải. Ghi đè giá trị của cột ở mỗi nút, theo dõi cột nhỏ nhất và lớn nhất, rồi đọc các cột theo thứ tự ở cuối.
Lời giải
Mỗi cột hiển thị nút sâu nhất của nó, nếu hòa thì chọn nút xuất hiện sau trong thứ tự theo từng tầng. Tìm kiếm theo chiều rộng thăm các nút theo thứ tự từng tầng, vì vậy nút cuối cùng được thăm trong một cột là đáp án: để mỗi nút ghi đè lên vị trí của cột đó.
Chiều sâu, nút sâu nhất trong mỗi cột
Ý tưởng
Duyệt cây theo chiều sâu, gán cho mỗi nút cột và độ sâu của nó, đồng thời lưu độ sâu và giá trị của nút sâu nhất đã gặp cho đến lúc đó trong từng cột.
Duyệt cây con bên trái trước cây con bên phải sẽ gặp các nút ở cùng một mức theo thứ tự từ trái sang phải, vì vậy nút bạn cần tìm là nút được gặp sau cùng: thay thế khi độ sâu lớn hơn hoặc bằng. Trong ví dụ 1, quá trình duyệt gặp 7 rồi đến 1, cả hai đều ở độ sâu 2 trong cột 0, và 1 được chọn cho cột đó.
Các cột tạo thành một khoảng liên tục, vì vậy hãy theo dõi cột nhỏ nhất và lớn nhất rồi đọc mọi cột ở giữa. Mỗi nút được duyệt một lần, nên thời gian chạy là O(n), còn đệ quy sâu h lời gọi với cây có h mức.
Thuật toán
- Thăm nút gốc với cột 0 và độ sâu 0.
- Bỏ qua chỉ số
inếu nó vượt quá cuối mảng hoặcroot[i]là-1. - Nếu cột chưa có nút nào hoặc độ sâu này lớn hơn hoặc bằng độ sâu đã lưu, hãy lưu độ sâu và giá trị này.
- Thăm
2i+1với cộtcol-1, sau đó thăm2i+2với cộtcol+1, cả hai đều sâu hơn một cấp. - Trả về các giá trị đã lưu, bắt đầu từ cột nhỏ nhất.
def bottomView(root):
n = len(root)
best = {} # column -> (depth, value) of the deepest node found so far
def visit(i, col, depth):
if i >= n or root[i] == -1:
return
# Replace on a greater or equal depth: on a tie, the node
# met last is the one further right, which comes later 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 cuối cùng trong mỗi cột
Ý tưởng
Một hàng đợi mà mỗi nút rời khỏi hàng sẽ thêm nút con bên trái, rồi đến nút con bên phải, đưa các nút ra theo thứ tự từng mức: mọi nút ở độ sâu d trước bất kỳ nút nào ở độ sâu d+1, và mỗi mức đi từ trái sang phải. Trong số các nút cùng cột, nút sâu nhất được đưa ra sau cùng; nếu cùng độ sâu, nút ở bên phải hơn được đưa ra sau. Vì vậy, khi mỗi nút ghi đè vị trí của cột đó, mỗi vị trí sẽ giữ nút được nhìn thấy từ bên dưới, không cần so sánh độ sâu.
Trong ví dụ 1, 7 ghi vào cột 0 và 1 ghi đè lên nó.
Mỗi nút cần một lượt thăm trong hàng đợi và một lần ghi vào bản đồ: thời gian O(n). Hàng đợi có thể chứa cả một mức, khoảng một nửa số nút, nên không gian cần dùng 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 và lưuroot[i]dướicol, thay thế mọi giá trị trước đó. - Thêm (
2i+1,col-1), rồi (2i+2,col+1) cho mỗi nút con nằm trong mảng và không phải-1. - Lặp lại cho đến khi hàng đợi trống, đồng thời theo dõi cột nhỏ nhất và lớn nhất.
- Trả về các giá trị của bản đồ, bắt đầu từ cột nhỏ nhất.
from collections import deque
def bottomView(root):
n = len(root)
bottom = {} # column -> value of the last 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 deeper nodes later, and right after left
# on one level, so the last node to reach a column is the one seen from below.
bottom[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 [bottom[col] for col in range(min(bottom), max(bottom) + 1)]
Lỗi thường gặp và trường hợp biên
- Chỉ thay thế khi độ sâu lớn hơn trong phiên bản duyệt theo chiều sâu. Ví dụ 1 khi đó giữ lại
7thay vì1. - Giữ lại nút đầu tiên mà tìm kiếm theo chiều rộng gặp trong mỗi cột. Đó là hình chiếu từ trên xuống.
- Thêm nút con bên phải vào hàng đợi trước nút con bên trái. Khi đó, mỗi mức sẽ xuất hiện từ phải sang trái và khi có các nút cùng mức, bạn sẽ chọn nhầm nút.
- Lấy các nút lá làm đáp án. Trong ví dụ 1, nút lá
7bị1che khuất, còn trong ví dụ 2, nút4có nút con nhưng lại nằm ở đáy cột 0. - Sắp xếp các cột dưới dạng văn bản. Khi là chuỗi,
"-10"nằm giữa"-1"và"-2".
Câu hỏi thường gặp3
Hình chiếu từ dưới của cây nhị phân là gì?
Gán cột 0 cho nút gốc, mỗi bước sang trái giảm một cột và mỗi bước sang phải tăng một cột. Góc nhìn từ dưới lên liệt kê nút sâu nhất của mỗi cột, từ trái sang phải. Bài toán này xử lý trường hợp hai nút có cùng độ sâu bằng thứ tự theo tầng, giữ lại nút xuất hiện sau.
Độ phức tạp thời gian của phép tìm hình chiếu từ dưới lên của cây nhị phân là bao nhiêu?
O(n) với n nút: tìm kiếm theo chiều rộng xử lý mỗi nút một lần và ghi vào một bản đồ băm một lần. Bản đồ có thứ tự như bản đồ cây làm tăng thêm hệ số log w với w cột.
Sự khác nhau giữa chế độ xem từ trên xuống và chế độ xem từ dưới lên là gì?
Cả hai đều chọn một nút cho mỗi cột. Góc nhìn từ trên chọn nút nông nhất, còn góc nhìn từ dưới chọn nút sâu nhất. Trong phiên bản duyệt theo chiều rộng, góc nhìn từ trên giữ lại giá trị đầu tiên mà một cột nhận được; góc nhìn từ dưới ghi đè giá trị đó mỗi lầ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 bottomView(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, 1, 9, 6]