Invert Binary Tree
Bạn nhận được một cây nhị phân được lưu trong mảng tree theo thứ tự từng mức. Gốc nằm ở chỉ số 0, các nút con của nút ở 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.
Đảo ngược cây: hoán đổi nút con bên trái và bên phải của mọi nút để toàn bộ cây trở thành ảnh phản chiếu của nó. Trả về cây đã đảo ngược theo cùng định dạng, không có phần tử -1 ở cuối.
Hàm
- treeinteger-array
- cây nhị phân theo thứ tự từng cấp, với -1 biểu thị vị trí trống
- Trả vềinteger-array
- cây phản chiếu theo thứ tự từng tầng, không có các mục -1 ở cuối
Ràng buộc
1 ≤ tree.length ≤ 16383- Mỗi
tree[i]là-1hoặc một giá trị thỏa mãn0 ≤ tree[i] ≤ 1000. tree[0]không bao giờ là-1, vì vậy cây có ít nhất một nút.- Mảng (array) có thể kết thúc bằng các mục
-1bổ sung 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à độ sâu tối đa là
14.
Ví dụ
- Đầu vào
- tree = [5, 3, 8, 1, 4, -1, 9]
- Đầu ra
- [5, 8, 3, 9, -1, 4, 1]
- Giải thích
- Các nút con của nút gốc
3và8đổi chỗ cho nhau. Bên dưới chúng,1và4nằm dưới3đổi thành4và1, còn8, trước đó chỉ có nút con bên phải là9, giờ có nút đó ở bên trái.
- Đầu vào
- tree = [2, 7, -1, 6]
- Đầu ra
- [2, -1, 7, -1, -1, -1, 6]
- Giải thích
- Chuỗi
2,7,6nghiêng sang trái, nên ảnh đối xứng của nó nghiêng sang phải:7chuyển từ chỉ số1sang2và6từ3sang6. Đáp án dài hơn đầu vào.
- Đầu vào
- tree = [1, -1, -1]
- Đầu ra
- [1]
- Giải thích
- Một nút đơn lẻ đối xứng với chính nó. Hai phần tử
-1là phần đệm, và đáp án loại bỏ mọi-1ở cuối.
Câu hỏi mở rộng
Làm thế nào để kiểm tra xem một cây có đối xứng với chính nó hay không bằng cách dùng cùng các cặp chỉ số mà không cần tạo bản sao đảo ngược?
Gợi ý
Mở từng gợi ý một. Mỗi gợi ý tiết lộ thêm một chút.
Nút gốc vẫn ở chỉ số
0. Trong cây được phản chiếu, nút con trái của nó sẽ nằm ở đâu? Hãy nghĩ xem vị trí của một nút phụ thuộc thế nào vào vị trí của nút cha.Nếu nút ở chỉ số
srcđến chỉ sốdst, nút con bên trái của nó đến2*dst+2và nút con bên phải đến2*dst+1. Mỗi nút luôn ở đúng tầng của nó, vì vậy đầu ra được làm tròn lên đến số tầng nguyên luôn có đủ chỗ.Điền
-1vào mảng đầu ra, sau đó duyệt bằng hàng đợi các cặp bắt đầu từ(0, 0). Với mỗi cặp, sao chép giá trị sang vị trí tương ứng và đưa các nút con thực vào hàng đợi với đích đã hoán đổi. Cuối cùng, loại bỏ các phần tử-1ở cuối.
Lời giải
Phép đối xứng hoán đổi cây con bên trái và bên phải của mỗi nút. Ở đây, vị trí của một nút là chỉ số của nó, nên việc hoán đổi hai cây con sẽ di chuyển mọi nút bên trong chúng. Thay vào đó, hãy xây dựng đáp án trong một mảng mới, đồng thời mang theo một cặp chỉ số trong quá trình duyệt: vị trí hiện tại của nút và vị trí đối xứng mà nó sẽ đến.
Đệ quy đặt mỗi nút tại chỉ số đối xứng của nó
Ý tưởng
Nút gốc vẫn ở chỉ số 0. Cây con bên trái của một nút trở thành cây con bên phải của bản sao đối xứng, nên nếu nút ở src chuyển đến dst, nút con bên trái chuyển đến 2*dst+2 và nút con bên phải chuyển đến 2*dst+1. Từ đó ta có place(src, dst), hàm này sao chép một giá trị rồi gọi đệ quy với cả hai nút con, đồng thời hoán đổi vị trí đích của chúng. Trong ví dụ đầu tiên, 3 ở chỉ số 1 chuyển đến 2, nên các nút con 1 và 4 chuyển đến 6 và 5.
Nút không bao giờ đổi tầng, nhưng chỉ số đối xứng của nó có thể vượt quá cuối đầu vào. Cấp phát đầu ra đủ cho các tầng hoàn chỉnh (1, 3, 7, 15, ...): trong ví dụ thứ hai, độ dài 4 trở thành 7, đủ chỗ cho 6 ở chỉ số 6. Mỗi nút chỉ được đặt một lần, nên thời gian chạy là O(n), đầu ra có kích thước O(n) và ngăn xếp lời gọi có tối đa 14 khung.
Thuật toán
- Làm tròn độ dài lên thành
size = 2^k - 1và điền-1vào mảng đầu ra có kích thước đó. - Viết hàm
place(src, dst): nếusrcnằm ngoài cuối mảng hoặctree[src]là-1, hãy trả về. - Nếu không, đặt
out[dst] = tree[src], rồi gọiplace(2*src+1, 2*dst+2)vàplace(2*src+2, 2*dst+1). - Gọi
place(0, 0), bỏ các phần tử-1ở cuối và trả về mảng đầu ra.
def invertTree(tree):
n = len(tree)
size = 1
while size < n: # round up to whole levels, so every mirrored index fits
size = 2 * size + 1
out = [-1] * size
def place(src, dst):
if src >= n or tree[src] == -1:
return
out[dst] = tree[src]
place(2 * src + 1, 2 * dst + 2) # the left subtree goes to the right
place(2 * src + 2, 2 * dst + 1) # the right subtree goes to the left
place(0, 0)
last = size - 1
while out[last] == -1: # trim the trailing -1 entries
last -= 1
return out[:last + 1]Tìm kiếm theo chiều rộng với hàng đợi các cặp chỉ số
Ý tưởng
Các cặp tương tự cũng dùng được với hàng đợi. Bắt đầu với (0, 0); mỗi cặp bạn lấy ra sẽ sao chép một giá trị và đưa từng nút con thực vào hàng đợi cùng với vị trí đích đã hoán đổi của nó. Phép hoán đổi được ghi vào chỉ số đích, vì mảng không thể hoán đổi hai cây con chỉ trong một bước. Mỗi nút thực đi vào hàng đợi đúng một lần, mang theo vị trí của nó.
Độ phức tạp thời gian là O(n), còn hàng đợi chứa O(w) cặp cho mức rộng nhất w. Không có ngăn xếp lời gọi nào để bị tràn.
Thuật toán
- Làm tròn độ dài lên đến số mức nguyên và tạo một mảng đầu ra có kích thước đó, điền
-1vào mảng. - Đưa cặp
(0, 0)vào hàng đợi. - Lấy một cặp
(src, dst)ở đầu hàng đợi và đặtout[dst] = tree[src]. - Đưa
(2*src+1, 2*dst+2)và(2*src+2, 2*dst+1)vào hàng đợi cho mỗi nút con nằm trong mảng và không phải-1. - Khi hàng đợi trống, bỏ các phần tử
-1ở cuối rồi trả về đầu ra.
def invertTree(tree):
n = len(tree)
size = 1
while size < n: # round up to whole levels, so every mirrored index fits
size = 2 * size + 1
out = [-1] * size
queue = [(0, 0)] # pairs: index in tree, index of its mirrored spot in out
head = 0
while head < len(queue):
src, dst = queue[head]
head += 1
out[dst] = tree[src]
left, right = 2 * src + 1, 2 * src + 2
if left < n and tree[left] != -1:
queue.append((left, 2 * dst + 2)) # the left child goes to the right
if right < n and tree[right] != -1:
queue.append((right, 2 * dst + 1)) # the right child goes to the left
last = size - 1
while out[last] == -1: # trim the trailing -1 entries
last -= 1
return out[:last + 1]
Lỗi thường gặp và trường hợp biên
- Đổi chỗ trực tiếp
tree[2*i+1]vàtree[2*i+2]. Việc đó chỉ đổi chỗ hai giá trị, không phải các cây con của chúng: trong ví dụ đầu tiên,1và4vẫn nằm dưới8. - Tạo đầu ra có độ dài bằng đầu vào. Giá trị
6trong ví dụ thứ hai nằm sau chỉ số cuối cùng của đầu vào. - Quên loại bỏ các phần tử
-1ở cuối, cả với đầu vào được đệm và với các cây phản chiếu kết thúc sớm hơn đầu vào. - Trong Lua và R, hãy nhớ rằng mảng bắt đầu từ 1. Giữ chỉ số bắt đầu từ 0 khi tính
2*i+1và đọctree[i + 1].
Câu hỏi thường gặp3
Đảo ngược cây nhị phân có nghĩa là gì?
Đảo ngược cây nhị phân sẽ biến cây thành hình ảnh phản chiếu của chính nó: tại mỗi nút, cây con bên trái và bên phải đổi chỗ cho nhau. Nút gốc vẫn giữ nguyên vị trí, còn nhánh bên trái trở thành nhánh bên phải. Đảo ngược hai lần sẽ đưa cây trở lại như ban đầu.
Độ phức tạp thời gian của việc đảo ngược cây nhị phân là bao nhiêu?
Mỗi nút được duyệt một lần, nên độ phức tạp thời gian là O(n). Đệ quy dùng O(h) không gian ngăn xếp cho cây có độ sâu h, còn hàng đợi dùng O(w) cho tầng rộng nhất. Trong phiên bản dùng mảng này, kết quả là một mảng mới, làm tăng thêm O(n).
Tại sao việc đảo ngược cây nhị phân lại đảo ngược từng cấp?
Phép đối xứng lật trái và phải ở mọi nơi, nên các nút ở mỗi tầng xuất hiện theo thứ tự ngược lại. Trong thứ tự theo tầng, lát cắt của mỗi tầng được đảo ngược: lát cắt [1, 4, -1, 9] trở thành [9, -1, 4, 1]. Đảo ngược từng tầng sau khi thêm -1 vào tầng cuối cùng là cách giải thứ ba có độ phức tạp O(n) cho cách biểu diễn này.
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 invertTree(tree):
# Viết code ở đâyKiểm thử #1
Kiểm thử #2
Kiểm thử #3
Đối số
tree = [5, 3, 8, 1, 4, -1, 9]
Đầu ra mong đợi
[5, 8, 3, 9, -1, 4, 1]