Subtree of Another Tree
Bạn có hai cây nhị phân, root và subRoot, mỗi cây được lưu trong một mảng theo thứ tự từng tầng: nút gốc ở chỉ số 0, các nút con của chỉ số i ở vị trí 2*i+1 (bên trái) và 2*i+2 (bên phải), còn -1 biểu thị một vị trí trống. Mảng có thể kết thúc bằng các phần tử -1 thừa.
Trả về true nếu có một nút nào đó trong root mà toàn bộ các nút hậu duệ của nó có hình dạng và giá trị khớp chính xác với subRoot, nếu không thì trả về false. Toàn bộ cây root cũng được tính.
Hàm
- rootinteger-array
- cây cần tìm kiếm, theo thứ tự từng mức, dùng -1 cho vị trí trống
- subRootinteger-array
- cây cần tìm, theo cùng định dạng
- Trả vềboolean
- true nếu subRoot xuất hiện trong root dưới dạng một nút có đầy đủ các nút con, nếu không thì false
Ràng buộc
1 ≤ root.length ≤ 32767và1 ≤ subRoot.length ≤ 32767- Mỗi mục là
-1hoặc một giá trị từ0đến104. root[0]vàsubRoot[0]không bao giờ là-1, vì vậy cả hai cây đều có ít nhất một nút.- Mỗi mảng có thể kết thúc bằng các mục
-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à mỗi cây có độ sâu tối đa là
14. - Giá trị có thể lặp lại.
Ví dụ
- Đầu vào
- root = [6, 2, 8, 1, 3, -1, 9]subRoot = [2, 1, 3]
- Đầu ra
- true
- Giải thích
- Nút
2ở chỉ số1có các nút con là1và3, cả hai đều là nút lá. Đó chính xác làsubRoot.
- Đầu vào
- root = [6, 2, 8, 1, 3, -1, 9, -1, -1, 0]subRoot = [2, 1, 3]
- Đầu ra
- false
- Giải thích
- Nút
3bên dưới nút2giờ có nút con0. Cây con gồm tất cả các hậu duệ, nên nút2kéo theo bốn nút, nhiều hơn ba nút củasubRoot, và không có nút nào khác chứa2.
- Đầu vào
- root = [5, 5, 7]subRoot = [5]
- Đầu ra
- true
- Giải thích
- Nút gốc
5có hai nút con, nên không khớp với nút đơn5. Nút5ở chỉ số1là nút lá và khớp.
Câu hỏi mở rộng
Bạn có thể tạo dấu vân tay cho mọi cây con từ giá trị của nó và dấu vân tay của các cây con của nó, để việc so sánh với subRoot chỉ tốn O(1) không? Bạn xử lý thế nào khi hai cây con khác nhau bị trùng dấu vân tay?
Gợi ý
Mở từng gợi ý một. Mỗi gợi ý tiết lộ thêm một chút.
Nếu
subRootxuất hiện bên trongroot, đỉnh của nó nằm trên một nút củaroot. Đó có thể là những nút nào, và bạn cần kiểm tra gì ở đó?Viết một hàm để xác định hai cây có giống hệt nhau hay không: cả hai đều rỗng, hoặc có cùng giá trị ở nút trên cùng, cùng cây con trái và cùng cây con phải. Nếu một bên rỗng còn bên kia có nút thì hai cây khác nhau.
Chạy phép kiểm tra đó từ mỗi nút của
root. Để bỏ qua hầu hết các nút, trước tiên hãy tính chiều cao của từng nút: chỉ nút có chiều cao bằng chiều cao củasubRootmới có thể khớp.
Lời giải
Bài toán gồm hai phần: xác định xem hai cây có giống hệt nhau không và chọn vị trí trong root để thực hiện kiểm tra đó. Chỉ nút có chiều cao bằng chiều cao của subRoot mới có thể khớp, và các nút có cùng chiều cao không bao giờ chồng lấp, vì vậy chỉ kiểm tra các nút đó sẽ duyệt mỗi nút của root nhiều nhất một lần.
So sánh tại từng nút
Ý tưởng
Một vị trí i là hợp lệ khi i nằm trong mảng và giá trị ở đó không phải là -1. Hai cây giống hệt nhau khi cả hai đều rỗng, hoặc khi nút gốc của chúng có cùng giá trị, đồng thời cây con bên trái và cây con bên phải cũng giống hệt nhau. same(i, j) duyệt đồng thời root từ i và subRoot từ j. Nếu subRoot xuất hiện trong root, nút gốc của nó nằm trên một nút nào đó, vì vậy hãy thử same(i, 0) với mọi i hợp lệ.
Mỗi lần kiểm tra có thể chạy đến m bước trước khi thất bại, bắt đầu từ mỗi nút trong số n nút: O(n·m). Ở đây, giới hạn độ sâu giúp giảm số bước: một nút chỉ được duyệt bởi các lần kiểm tra bắt đầu từ chính nó hoặc từ phía trên nó, tối đa 14 lần.
Thuật toán
- Viết
same(i, j). Nếu vị tríitrongrootvà vị tríjtrongsubRootđều trống, trả vềtrue; nếu chỉ một vị trí trống, trả vềfalse. - Nếu hai giá trị khác nhau, trả về
false. Nếu không, trả vềsame(2*i+1, 2*j+1)vàsame(2*i+2, 2*j+2). - Trả về
truenếusame(i, 0)đúng với một chỉ số thực nào đó củaroot, và trả vềfalsenếu không.
def isSubtree(root, subRoot):
n, m = len(root), len(subRoot)
def same(i, j):
# True if the subtree at root[i] equals the subtree at subRoot[j].
a = i < n and root[i] != -1
b = j < m and subRoot[j] != -1
if not a or not b:
return a == b # two empty spots match, one empty spot does not
if root[i] != subRoot[j]:
return False
return same(2 * i + 1, 2 * j + 1) and same(2 * i + 2, 2 * j + 2)
# Try every real node of root as the top of a match.
for i in range(n):
if root[i] != -1 and same(i, 0):
return True
return FalseChỉ so sánh khi chiều cao bằng nhau
Ý tưởng
Các cây giống hệt nhau có cùng chiều cao, được tính bằng số nút trên đường đi dài nhất xuống dưới. Vì vậy, chỉ cần kiểm tra các nút trong root có chiều cao bằng chiều cao của subRoot. Trong ví dụ đầu tiên, subRoot có chiều cao 2, còn trong root chỉ có nút 2 và 8 như vậy.
Một nút luôn cao hơn mọi nút bên dưới, nên hai nút có cùng chiều cao không bao giờ nằm lồng bên trong nhau. Mỗi lần kiểm tra chỉ duyệt cây con bắt đầu từ nút tương ứng, vì vậy tổng các lần kiểm tra duyệt mỗi nút trong root nhiều nhất một lần.
Các nút con nằm ở chỉ số lớn hơn nút cha, nên khi duyệt từ chỉ số cuối trở về trước, ta sẽ đến cả hai nút con trước nút cha. Hai lần duyệt để tính chiều cao và các lần kiểm tra mất O(n + m) thời gian.
Thuật toán
- Điền mảng chiều cao cho mỗi cây, quét từ chỉ số cuối cùng xuống
0: vị trí trống nhận giá trị0, nút thật nhận giá trị1cộng với chiều cao lớn hơn trong hai nút con. - Với mỗi chỉ số
icủarootcó chiều cao bằng chiều cao tại chỉ số0củasubRoot, chạysame(i, 0)và trả vềtruenếu điều kiện này đúng. - Trả về
false.
def isSubtree(root, subRoot):
n, m = len(root), len(subRoot)
def same(i, j):
# True if the subtree at root[i] equals the subtree at subRoot[j].
a = i < n and root[i] != -1
b = j < m and subRoot[j] != -1
if not a or not b:
return a == b # two empty spots match, one empty spot does not
if root[i] != subRoot[j]:
return False
return same(2 * i + 1, 2 * j + 1) and same(2 * i + 2, 2 * j + 2)
def heights(tree):
# Children sit at larger indexes, so a backward scan finishes them first.
h = [0] * len(tree)
for i in range(len(tree) - 1, -1, -1):
if tree[i] != -1:
left = h[2 * i + 1] if 2 * i + 1 < len(tree) else 0
right = h[2 * i + 2] if 2 * i + 2 < len(tree) else 0
h[i] = 1 + max(left, right)
return h
target = heights(subRoot)[0]
rootHeights = heights(root)
# Only a node of the same height can match, and no two such nodes overlap.
for i in range(n):
if rootHeights[i] == target and same(i, 0):
return True
return False
Lỗi thường gặp và trường hợp biên
- Chấp nhận một phần khớp, như trong ví dụ thứ hai.
samephải trả về false khirootcó một nút ở vị trísubRootđể trống. - Dừng lại ở nút đầu tiên có giá trị phù hợp. Trong ví dụ thứ ba, nút gốc
5không khớp, còn nút lá5bên dưới thì khớp. - So sánh các phần của hai mảng. Các nút của cây con trải rộng qua nhiều tầng, và mảng nào cũng có thể kết thúc bằng các phần tử
-1dư. Chỉ số nằm ngoài mảng tương ứng với một vị trí trống, giống như-1. - Trong Lua và R, giữ chỉ số nút bắt đầu từ 0 để tính toán
2*i+1và đọcroot[i + 1].
Câu hỏi thường gặp3
Độ phức tạp thời gian của bài toán Cây con của một cây khác là gì?
So sánh từ mọi nút có độ phức tạp O(n·m) trong trường hợp xấu nhất, với n và m là kích thước của hai cây. Chỉ so sánh tại các nút có chiều cao bằng chiều cao của subRoot giúp giảm độ phức tạp xuống còn O(n + m), vì khi đó mỗi nút của root chỉ được duyệt nhiều nhất một lần.
Có thể giải bài toán cây con của một cây khác bằng cách so khớp chuỗi không?
Đúng. Hãy viết mỗi cây theo thứ tự trước, dùng # cho mỗi nút con trống và đặt dấu phân cách trước mỗi giá trị: ,2,1,#,#,3,#,#. subRoot là cây con khi chuỗi của nó xuất hiện bên trong chuỗi của root, và KMP tìm chuỗi đó trong O(n + m). Nếu không có dấu đánh dấu, các hình dạng khác nhau sẽ tạo ra cùng một chuỗi; nếu không có dấu phân cách, 12 sẽ chứa 2.
Nút khớp với subRoot nhưng có thêm nút con thì có được tính không?
Không. Cây con gồm một nút cùng tất cả các nút con cháu của nó, vì vậy mọi nút bên dưới nút khớp cũng phải xuất hiện trong subRoot. Trong ví dụ thứ hai, số 0 thừa bên dưới 3 khiến đáp án là false.
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 isSubtree(root, subRoot):
# Viết code ở đâyKiểm thử #1
Kiểm thử #2
Kiểm thử #3
Đối số
root = [6, 2, 8, 1, 3, -1, 9] subRoot = [2, 1, 3]
Đầu ra mong đợi
true