Làm thế nào để tìm tổ tiên chung thấp nhất của hai nút trong bất kỳ cây nhị phân nào?


186

Cây nhị phân ở đây có thể không nhất thiết là Cây tìm kiếm nhị phân.
Cấu trúc có thể được lấy là -

struct node {
    int data;
    struct node *left;
    struct node *right;
};

Giải pháp tối đa tôi có thể làm việc với một người bạn là một thứ gì đó thuộc loại này -
Hãy xem xét cây nhị phân này :

Cây nhị phân

Sản lượng truyền tải theo thứ tự - 8, 4, 9, 2, 5, 1, 6, 3, 7

Và sản lượng truyền qua bưu điện - 8, 9, 4, 5, 2, 6, 7, 3, 1

Vì vậy, ví dụ, nếu chúng ta muốn tìm tổ tiên chung của các nút 8 và 5, thì chúng ta tạo một danh sách tất cả các nút nằm trong khoảng từ 8 đến 5 trong giao diện cây inorder, trong trường hợp này xảy ra là [4, 9 , 2]. Sau đó, chúng tôi kiểm tra nút nào trong danh sách này xuất hiện cuối cùng trong giao dịch postorder, là 2. Do đó, tổ tiên chung cho 8 và 5 là 2.

Độ phức tạp của thuật toán này, tôi tin là O (n) (O (n) cho các giao dịch inorder / postorder, phần còn lại của các bước là O (n) vì chúng không hơn gì các lần lặp đơn giản trong mảng). Nhưng có một cơ hội mạnh mẽ rằng điều này là sai. :-)

Nhưng đây là một cách tiếp cận rất thô thiển, và tôi không chắc liệu nó có bị hỏng trong một số trường hợp không. Có giải pháp nào khác (có thể tối ưu hơn) cho vấn đề này không?


6
Vì tò mò, công dụng thực tế của việc này là gì?
David Brunelle

19
@David: Trả lời truy vấn LCA khá hữu ích. LCA + Suffix cây = thuật toán liên quan đến chuỗi mạnh mẽ.

44
Và khi tôi hỏi một câu hỏi tương tự, nó đã được bình chọn với các bình luận như câu hỏi phỏng vấn của nó. Tính hai mặt của SO? :(
some_other_guy

5
@Siddant +1 cho các chi tiết được đưa ra trong câu hỏi. :)
amod

5
@DavidBrunelle Một ứng dụng thực tế của tính toán LCA: đó là một tính toán thiết yếu khi kết xuất các trang web, đặc biệt khi tính toán Cascading Style Sheets (CSS) có thể áp dụng cho một phần tử DOM cụ thể.
zc22

Câu trả lời:


73

Nick Johnson đúng rằng thuật toán độ phức tạp thời gian O (n) là thuật toán tốt nhất bạn có thể làm nếu bạn không có con trỏ cha.) Đối với phiên bản đệ quy đơn giản của thuật toán đó, hãy xem mã trong bài đăng của Kinding chạy trong thời gian O (n) .

Nhưng hãy nhớ rằng nếu các nút của bạn có con trỏ cha, thuật toán được cải thiện là có thể. Đối với cả hai nút trong câu hỏi, xây dựng một danh sách chứa đường dẫn từ gốc đến nút bằng cách bắt đầu tại nút và phía trước chèn cha.

Vì vậy, với 8 trong ví dụ của bạn, bạn nhận được (hiển thị các bước): {4}, {2, 4}, {1, 2, 4}

Thực hiện tương tự cho nút khác của bạn trong câu hỏi, dẫn đến (các bước không được hiển thị): {1, 2}

Bây giờ hãy so sánh hai danh sách bạn đã tìm kiếm phần tử đầu tiên trong đó danh sách khác nhau hoặc phần tử cuối cùng của một trong các danh sách, tùy theo cái nào đến trước.

Thuật toán này yêu cầu thời gian O (h) trong đó h là chiều cao của cây. Trong trường hợp xấu nhất O (h) tương đương với O (n), nhưng nếu cây cân bằng, đó chỉ là O (log (n)). Nó cũng đòi hỏi không gian O (h). Một phiên bản cải tiến có thể chỉ sử dụng không gian không đổi, với mã được hiển thị trong bài đăng của CEGRD


Bất kể cây được xây dựng như thế nào, nếu đây sẽ là thao tác bạn thực hiện nhiều lần trên cây mà không thay đổi nó ở giữa, có những thuật toán khác bạn có thể sử dụng yêu cầu chuẩn bị thời gian O (n) [tuyến tính], nhưng sau đó tìm bất kỳ cặp chỉ mất thời gian O (1) [không đổi]. Để tham khảo các thuật toán này, hãy xem trang vấn đề tổ tiên chung thấp nhất trên Wikipedia . (Tín dụng cho Jason để đăng liên kết này ban đầu)


1
Đó là công việc nếu con trỏ cha được đưa ra. Các nút trong cây giống như cấu trúc mà tôi đã đưa ra trong câu hỏi của mình - chỉ là các con trỏ con trái / phải, không có con trỏ cha. Có giải pháp O (log (n)) nào không nếu có sẵn con trỏ cha và cây không phải là cây tìm kiếm nhị phân và chỉ là cây nhị phân?
Siddhant

2
Nếu bạn không có cách đặc biệt nào để tìm đường dẫn giữa nút cha và nút đã cho, thì trung bình sẽ mất thời gian O (n) để tìm thấy nó. Điều đó sẽ làm cho không thể có thời gian O (log (n)). Tuy nhiên, chi phí một lần O (n), với tìm kiếm cặp O (1) có thể là lựa chọn tốt nhất của bạn nếu bạn sẽ thực hiện thao tác này nhiều lần mà không thay đổi cây ở giữa. Mặt khác, nếu có thể, bạn nên thêm con trỏ cha. Nó có thể làm cho khá nhiều thuật toán tiềm năng nhanh hơn, nhưng tôi khá chắc chắn rằng nó không thay đổi thứ tự của bất kỳ thuật toán hiện có nào. Hi vọng điêu nay co ich.
Kevin Cathcart

1
Cách tiếp cận này có thể được thực hiện bằng cách sử dụng bộ nhớ O (1) - xem giải pháp của Artelius (và những người khác) tại stackoverflow.com/questions/1594061/
Kẻ

@Tom: Thật vậy, điều đó sẽ làm việc để giới hạn độ phức tạp của bộ nhớ ở O (1) cho thuật toán dựa trên danh sách. Rõ ràng điều đó có nghĩa là lặp đi lặp lại qua chính cây một lần cho mỗi bên để có được độ sâu của các nút, và sau đó lần thứ hai (một phần) để tìm tổ tiên chung. O (h) thời gian và không gian O (1) rõ ràng là tối ưu cho trường hợp có con trỏ cha mẹ và không thực hiện tiền mã hóa O (n).
Kevin Cathcart

1
@ALBI O(h)chỉ O(log(n))khi cây cân bằng. Đối với bất kỳ cây nào, có thể là nhị phân hay không, nếu bạn có con trỏ cha, bạn có thể xác định đường dẫn từ một chiếc lá đến gốc theo O(h)thời gian, chỉ bằng cách theo con trỏ cha theo hthời gian. Điều đó cho bạn con đường từ lá đến gốc. Nếu các đường dẫn được lưu trữ dưới dạng ngăn xếp, thì việc lặp lại ngăn xếp sẽ cung cấp cho bạn đường dẫn từ gốc đến lá. Nếu bạn thiếu con trỏ cha mẹ và không có cấu trúc đặc biệt cho cây, thì việc tìm đường dẫn từ gốc đến lá sẽ mất O(n)thời gian.
Kevin Cathcart

107

Bắt đầu từ rootnút và di chuyển xuống dưới nếu bạn tìm thấy bất kỳ nút nào có phoặc qlà con trực tiếp của nó thì đó là LCA. (chỉnh sửa - điều này sẽ là nếup hoặc qlà giá trị của nút, trả về nó. Nếu không, nó sẽ thất bại khi một trong hai phoặc qlà con trực tiếp của cái kia.)

Khác nếu bạn tìm thấy một nút với p cây con bên phải (hoặc bên trái) và qtrong cây con bên trái (hoặc bên phải) thì đó là LCA.

Mã cố định trông như sau:

treeNodePtr findLCA(treeNodePtr root, treeNodePtr p, treeNodePtr q) {

        // no root no LCA.
        if(!root) {
                return NULL;
        }

        // if either p or q is the root then root is LCA.
        if(root==p || root==q) {
                return root;
        } else {
                // get LCA of p and q in left subtree.
                treeNodePtr l=findLCA(root->left , p , q);

                // get LCA of p and q in right subtree.
                treeNodePtr r=findLCA(root->right , p, q);

                // if one of p or q is in leftsubtree and other is in right
                // then root it the LCA.
                if(l && r) {
                        return root;
                }
                // else if l is not null, l is LCA.
                else if(l) {
                        return l;
                } else {
                        return r;
                }
        }
}

Mã dưới đây không thành công khi là con trực tiếp của người khác.

treeNodePtr findLCA(treeNodePtr root, treeNodePtr p, treeNodePtr q) {

        // no root no LCA.
        if(!root) {
                return NULL;
        }

        // if either p or q is direct child of root then root is LCA.
        if(root->left==p || root->left==q || 
           root->right ==p || root->right ==q) {
                return root;
        } else {
                // get LCA of p and q in left subtree.
                treeNodePtr l=findLCA(root->left , p , q);

                // get LCA of p and q in right subtree.
                treeNodePtr r=findLCA(root->right , p, q);

                // if one of p or q is in leftsubtree and other is in right
                // then root it the LCA.
                if(l && r) {
                        return root;
                }
                // else if l is not null, l is LCA.
                else if(l) {
                        return l;
                } else {
                        return r;
                }
        }
}

Mã đang hoạt động


2
giải pháp tao nhã, nhưng gốc == p | | root == q => trả về bit gốc có vẻ quá mức. Điều gì xảy ra nếu nó bật ra root là p / q, nhưng nút tìm kiếm khác không thực sự nằm trong cây?
Ian Durkan

15
Tôi đoán mã này thất bại khi p hoặc q là một giá trị không nằm trong cây nhị phân. Tôi có đúng không Ví dụ LCA (8,20). mã ur trả về 8. nhưng 20 không có trong cây nhị phân
javaMan

3
Chi phí cho giải pháp này là bao nhiêu? Có hiệu quả không? Nó xuất hiện để tiếp tục tìm kiếm ngay cả khi đã tìm thấy cả p và q. Có phải vì khả năng p và q có thể không phải là duy nhất trong cây vì nó không phải là BST và có thể chứa các bản sao?
MikeB

3
@MikeB, giải pháp này chắc chắn là O (n), vì bạn đi qua mỗi nút chỉ một lần trong trường hợp xấu nhất. Peter Lee, đây là cách hiệu quả nhất mà bạn có thể thực hiện mà không cần sử dụng con trỏ cha mẹ. Bạn có một giải pháp tốt hơn?
gsingh2011

8
giải pháp không hoàn hảo đầu tiên nên được xóa để nó không bị phân tâm
Zinan Xing

50

Đây là mã làm việc trong JAVA

public static Node LCA(Node root, Node a, Node b) {
   if (root == null) {
       return null;
   }

   // If the root is one of a or b, then it is the LCA
   if (root == a || root == b) {
       return root;
   }

   Node left = LCA(root.left, a, b);
   Node right = LCA(root.right, a, b);

   // If both nodes lie in left or right then their LCA is in left or right,
   // Otherwise root is their LCA
   if (left != null && right != null) {
      return root;
   }

   return (left != null) ? left : right; 
}

4
Điều này không hoạt động khi một nút không tồn tại trong cây.
Pratik Khadloya

bạn sẽ tối ưu hóa mã của mình nếu cây đã cho là BST?
Mona Jalal

1
"Nếu gốc là một trong a hoặc b, thì đó là LCA." Điều này có thể không đúng. Những gì bạn biết vào thời điểm này là bạn không cần phải kiểm tra bất kỳ đứa trẻ nào của nó để tìm LCA. Điều này xảy ra bởi vì sau này chúng ta có thể kiểm tra xem cha mẹ của root có khớp với cả hai nhánh (LCA là cha mẹ) hay chỉ một trong số chúng (trong trường hợp đó có thể là LCA, hoặc tổ tiên thậm chí còn lớn hơn có thể là LCA ).
andresp

28

Các câu trả lời cho đến nay sử dụng đệ quy hoặc lưu trữ, ví dụ, một đường dẫn trong bộ nhớ.

Cả hai cách tiếp cận này có thể thất bại nếu bạn có một cây rất sâu.

Đây là tôi đưa ra câu hỏi này. Khi chúng ta kiểm tra độ sâu (khoảng cách từ gốc) của cả hai nút, nếu chúng bằng nhau, thì chúng ta có thể di chuyển lên trên một cách an toàn từ cả hai nút về phía tổ tiên chung. Nếu một trong những độ sâu lớn hơn thì chúng ta nên di chuyển lên từ nút sâu hơn trong khi vẫn ở trong nút khác.

Đây là mã:

findLowestCommonAncestor(v,w):
  depth_vv = depth(v);
  depth_ww = depth(w);

  vv = v; 
  ww = w;

  while( depth_vv != depth_ww ) {
    if ( depth_vv > depth_ww ) {
      vv = parent(vv);
      depth_vv--;
    else {
      ww = parent(ww);
      depth_ww--;
    }
  }

  while( vv != ww ) {
    vv = parent(vv);
    ww = parent(ww);
  }

  return vv;    

Độ phức tạp thời gian của thuật toán này là: O (n). Độ phức tạp không gian của thuật toán này là: O (1).

Về tính toán độ sâu, trước tiên chúng ta có thể nhớ định nghĩa: Nếu v là gốc, độ sâu (v) = 0; Mặt khác, độ sâu (v) = độ sâu (cha (v)) + 1. Chúng ta có thể tính độ sâu như sau:

depth(v):
  int d = 0;
  vv = v;
  while ( vv is not root ) {
    vv = parent(vv);
    d++;
  }
  return d;

6
Cây nhị phân thường không có tham chiếu đến phần tử cha. Thêm một tham chiếu cha mẹ có thể được thực hiện mà không có bất kỳ vấn đề, nhưng tôi sẽ xem xét rằng không gian phụ trợ O (n).
John Kurlak

Có một giả định tinh tế trong giải pháp này. Nếu một nút là cha mẹ trực tiếp hoặc gián tiếp của nút kia (nghĩa là nút sâu hơn nằm trong một cây gốc ở nút nông hơn), giải pháp này trả về kết quả là cha mẹ của nút nông hơn. Tùy thuộc vào cách bạn xác định tổ tiên chung thấp nhất, đây có thể không phải là điều bạn muốn. Một số định nghĩa sẽ yêu cầu nút nông hơn phải là cha mẹ. Trong trường hợp này, bạn sẽ cần theo dõi nút nào là nông hơn và trả lại nút đó.
Srikanth

8

Chà, loại này phụ thuộc vào cách cấu trúc cây nhị phân của bạn. Có lẽ bạn có một số cách để tìm nút lá mong muốn được cung cấp gốc của cây - chỉ cần áp dụng điều đó cho cả hai giá trị cho đến khi các nhánh bạn chọn phân kỳ.

Nếu bạn không có cách nào để tìm chiếc lá mong muốn được cung cấp cho gốc, thì giải pháp duy nhất của bạn - cả trong hoạt động bình thường và tìm nút chung cuối cùng - là tìm kiếm mạnh mẽ của cây.


8

Điều này có thể được tìm thấy tại: - http://goursaha.freeoda.com/DataStr struct / LowestCommonAncestor.html

 tree_node_type *LowestCommonAncestor(
 tree_node_type *root , tree_node_type *p , tree_node_type *q)
 {
     tree_node_type *l , *r , *temp;
     if(root==NULL)
     {
        return NULL;
     }

    if(root->left==p || root->left==q || root->right ==p || root->right ==q)
    {
        return root;
    }
    else
    {
        l=LowestCommonAncestor(root->left , p , q);
        r=LowestCommonAncestor(root->right , p, q);

        if(l!=NULL && r!=NULL)
        {
            return root;
        }
        else
        {
        temp = (l!=NULL)?l:r;
        return temp;
        }
    }
}

bạn có thể vui lòng cho tôi biết mã của bạn sẽ hoạt động như thế nào nếu p có mặt nhưng q hoàn toàn không có trong cây? Tương tự cả p và q đều không có. Cảm ơn!!!
Thử

O lớn về thời gian là gì? Tôi nghĩ đó là O (n * log (n)), hai chậm.
Peter Lee


6

Để tìm ra tổ tiên chung của hai nút: -

  • Tìm nút Node1 đã cho trong cây bằng cách sử dụng tìm kiếm nhị phân và lưu tất cả các nút được truy cập trong quy trình này trong một mảng có tên A1. Thời gian - O (logn), Space - O (logn)
  • Tìm Node2 đã cho trong cây bằng cách sử dụng tìm kiếm nhị phân và lưu tất cả các nút được truy cập trong quy trình này trong một mảng có tên là A2. Thời gian - O (logn), Space - O (logn)
  • Nếu danh sách A1 hoặc danh sách A2 trống thì một nút không tồn tại nên không có tổ tiên chung.
  • Nếu danh sách A1 và danh sách A2 không trống thì hãy nhìn vào danh sách cho đến khi bạn tìm thấy nút không khớp. Ngay khi bạn tìm thấy một nút như vậy thì nút trước đó là tổ tiên chung.

Điều này sẽ làm việc cho cây tìm kiếm nhị phân.


2
Ông nói rõ rằng cây KHÔNG nhất thiết phải là BST.
Peter Lee

@Peter Lee - Logic trên sẽ hoạt động ngay cả đối với bất kỳ cây nhị phân nào với một thay đổi đơn giản. Thay vì tìm kiếm nhị phân của các nút đã cho, hãy áp dụng tìm kiếm tuyến tính (nghĩa là bất kỳ giao dịch nào nhưng phải giống nhau cho cả hai trường hợp). Thời gian chạy khóa học sẽ là O (n) thay vì O (logn). Trong thực tế, thuật toán này là mạnh nhất khi con trỏ cha mẹ không có sẵn. Thuật toán rucursive được đưa ra bởi nhiều người (viz. 'Codaddict') sẽ không hoạt động khi một trong các nút đã cho không thuộc về cây)
KGhatak


3

Thuật toán đệ quy dưới đây sẽ chạy trong O (log N) cho cây nhị phân cân bằng. Nếu một trong hai nút được truyền vào hàm getLCA () giống với gốc thì gốc sẽ là LCA và sẽ không cần thực hiện bất kỳ sự thu hồi nào.

Các trường hợp thử nghiệm. [1] Cả hai nút n1 & n2 đều ở trong cây và nằm ở hai bên của nút cha của chúng. [2] Hoặc nút n1 hoặc n2 là gốc, LCA là gốc. [3] Chỉ có n1 hoặc n2 trong cây, LCA sẽ là nút gốc của cây con bên trái của gốc cây hoặc LCA sẽ là nút gốc của cây con bên phải của gốc cây.

[4] Cả n1 hoặc n2 đều không có trong cây, không có LCA. [5] Cả n1 và n2 nằm trên một đường thẳng cạnh nhau, LCA sẽ là n1 hoặc n2, bao giờ cũng đóng vào gốc của cây.

//find the search node below root
bool findNode(node* root, node* search)
{
    //base case
    if(root == NULL)
        return false;

    if(root->val == search->val)
        return true;

    //search for the node in the left and right subtrees, if found in either return true
    return (findNode(root->left, search) || findNode(root->right, search));
}

//returns the LCA, n1 & n2 are the 2 nodes for which we are
//establishing the LCA for
node* getLCA(node* root, node* n1, node* n2)
{
    //base case
    if(root == NULL)
        return NULL;

    //If 1 of the nodes is the root then the root is the LCA
    //no need to recurse.
    if(n1 == root || n2 == root)
        return root;

    //check on which side of the root n1 and n2 reside
    bool n1OnLeft = findNode(root->left, n1);
    bool n2OnLeft = findNode(root->left, n2);

    //n1 & n2 are on different sides of the root, so root is the LCA
    if(n1OnLeft != n2OnLeft)
        return root;

    //if both n1 & n2 are on the left of the root traverse left sub tree only
    //to find the node where n1 & n2 diverge otherwise traverse right subtree
    if(n1OnLeft)
        return getLCA(root->left, n1, n2);
    else
        return getLCA(root->right, n1, n2);
}

3

Chỉ cần đi xuống từ toàn bộ cây rootmiễn là cả hai nút đã cho, nói pq , theo đó Tổ tiên phải được tìm thấy, nằm trong cùng một cây con (có nghĩa là các giá trị của chúng đều nhỏ hơn hoặc lớn hơn cả gốc).

Điều này đi thẳng từ gốc đến tổ tiên chung nhỏ nhất, không nhìn vào phần còn lại của cây, vì vậy nó khá nhanh như nó có được. Một vài cách để làm điều đó.

Lặp lại, không gian O (1)

Con trăn

def lowestCommonAncestor(self, root, p, q):
    while (root.val - p.val) * (root.val - q.val) > 0:
        root = (root.left, root.right)[p.val > root.val]
    return root

Java

public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
    while ((root.val - p.val) * (root.val - q.val) > 0)
        root = p.val < root.val ? root.left : root.right;
    return root;
}

trong trường hợp tràn, tôi sẽ làm (root.val - (dài) p.val) * (root.val - (dài) q.val)

Đệ quy

Con trăn

def lowestCommonAncestor(self, root, p, q):
    next = p.val < root.val > q.val and root.left or \
           p.val > root.val < q.val and root.right
    return self.lowestCommonAncestor(next, p, q) if next else root

Java

public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
    return (root.val - p.val) * (root.val - q.val) < 1 ? root :
           lowestCommonAncestor(p.val < root.val ? root.left : root.right, p, q);
}

2
Node *LCA(Node *root, Node *p, Node *q) {
  if (!root) return NULL;
  if (root == p || root == q) return root;
  Node *L = LCA(root->left, p, q);
  Node *R = LCA(root->right, p, q);
  if (L && R) return root;  // if p and q are on both sides
  return L ? L : R;  // either one of p,q is on one side OR p,q is not in L&R subtrees
}

2

Hãy xem xét cây này nhập mô tả hình ảnh ở đây

Nếu chúng ta thực hiện postorder và preorder traversal và tìm thấy người tiền nhiệm và người kế thừa chung đầu tiên, chúng ta sẽ có được tổ tiên chung.

đặt hàng sau => 0,2,1,5,4,6,3,8,10,11,9,14,15,13,12,7 đặt hàng trước => 7,3,1,0,2,6,4 , 5,12,9,8,11,10,13,15,14

  • ví dụ: 1

Ít nhất tổ tiên chung của 8,11

trong postorder chúng ta có => 9,14,15,13,12,7 sau 8 & 11 trong preorder chúng ta có => 7,3,1,0,2,6,4,5,12,9 trước 8 & 11

9 là số phổ biến đầu tiên xảy ra sau 8 & 11 trong bưu điện và trước 8 & 11 trong đặt hàng trước, do đó 9 là câu trả lời

  • ví dụ: 2

Ít nhất tổ tiên chung của 5,10

11,9,14,15,13,12,7 trong bưu điện 7,3,1,0,2,6,4 trong đơn đặt hàng trước

7 là số đầu tiên xuất hiện sau 5,10 trong bưu điện và trước 5,10 trong đặt hàng trước, do đó 7 là câu trả lời


2

Nếu nó là cây nhị phân đầy đủ với con của nút x là 2 * x và 2 * x + 1 thì có một cách nhanh hơn để làm điều đó

int get_bits(unsigned int x) {
  int high = 31;
  int low = 0,mid;
  while(high>=low) {
    mid = (high+low)/2;
    if(1<<mid==x)
      return mid+1;
    if(1<<mid<x) {
      low = mid+1;
    }
    else {
      high = mid-1;
    }
  }
  if(1<<mid>x)
    return mid;
  return mid+1;
}

unsigned int Common_Ancestor(unsigned int x,unsigned int y) {

  int xbits = get_bits(x);
  int ybits = get_bits(y);
  int diff,kbits;
  unsigned int k;
  if(xbits>ybits) {
    diff = xbits-ybits;
    x = x >> diff;
  }
  else if(xbits<ybits) {
    diff = ybits-xbits;
    y = y >> diff;
  }
  k = x^y;
  kbits = get_bits(k);
  return y>>kbits;  
}

Làm thế nào nó hoạt động

  1. nhận bit cần thiết để biểu diễn x & y mà sử dụng tìm kiếm nhị phân là O (log (32))
  2. tiền tố chung của ký hiệu nhị phân của x & y là tổ tiên chung
  3. cái nào được biểu thị bằng lớn hơn không có bit nào được đưa đến cùng bit bởi k >> diff
  4. k = x ^ y xóa tiền tố chung của x & y
  5. tìm bit đại diện cho hậu tố còn lại
  6. thay đổi x hoặc y bằng các bit hậu tố để có tiền tố chung là tổ tiên chung.

Điều này hoạt động vì về cơ bản chia số lớn hơn cho hai lần đệ quy cho đến khi cả hai số bằng nhau. Con số đó là tổ tiên chung. Chia rẽ có hiệu quả là sự thay đổi quyền thay đổi. Vì vậy, chúng ta cần tìm tiền tố chung của hai số để tìm tổ tiên gần nhất


2

Trong scala, bạn có thể:

  abstract class Tree
  case class Node(a:Int, left:Tree, right:Tree) extends Tree
  case class Leaf(a:Int) extends Tree

  def lca(tree:Tree, a:Int, b:Int):Tree = {
    tree match {
      case Node(ab,l,r) => {
        if(ab==a || ab ==b) tree else {
          val temp = lca(l,a,b);
          val temp2 = lca(r,a,b);
          if(temp!=null && temp2 !=null)
            tree
          else if (temp==null && temp2==null)
            null
          else if (temp==null) r else l
        }

      }
      case Leaf(ab) => if(ab==a || ab ==b) tree else null
    }
  }

1
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root==null || root == p || root == q){
            return root;
        }
        TreeNode left = lowestCommonAncestor(root.left,p,q);
        TreeNode right = lowestCommonAncestor(root.right,p,q);
        return left == null ? right : right == null ? left : root;
    }

0

Đây là cách làm của C ++. Đã cố gắng giữ thuật toán càng dễ hiểu càng tốt:

// Assuming that `BinaryNode_t` has `getData()`, `getLeft()` and `getRight()`
class LowestCommonAncestor
{
  typedef char type;    
  // Data members which would behave as place holders
  const BinaryNode_t* m_pLCA;
  type m_Node1, m_Node2;

  static const unsigned int TOTAL_NODES = 2;

  // The core function which actually finds the LCA; It returns the number of nodes found
  // At any point of time if the number of nodes found are 2, then it updates the `m_pLCA` and once updated, we have found it!
  unsigned int Search (const BinaryNode_t* const pNode)
  {
    if(pNode == 0)
      return 0;

    unsigned int found = 0;

    found += (pNode->getData() == m_Node1);
    found += (pNode->getData() == m_Node2);

    found += Search(pNode->getLeft()); // below condition can be after this as well
    found += Search(pNode->getRight());

    if(found == TOTAL_NODES && m_pLCA == 0)
      m_pLCA = pNode;  // found !

    return found;
  }

public:
  // Interface method which will be called externally by the client
  const BinaryNode_t* Search (const BinaryNode_t* const pHead,
                              const type node1,
                              const type node2)
  {
    // Initialize the data members of the class
    m_Node1 = node1;
    m_Node2 = node2;
    m_pLCA = 0;

    // Find the LCA, populate to `m_pLCANode` and return
    (void) Search(pHead);
    return m_pLCA;
  }
};

Làm thế nào để sử dụng nó:

LowestCommonAncestor lca;
BinaryNode_t* pNode = lca.Search(pWhateverBinaryTreeNodeToBeginWith);
if(pNode != 0)
  ...

0

Cách dễ nhất để tìm Tổ tiên chung thấp nhất là sử dụng thuật toán sau:

Kiểm tra nút gốc

nếu value1 và value2 hoàn toàn nhỏ hơn giá trị tại nút gốc 
    Kiểm tra cây con trái
khác nếu value1 và value2 hoàn toàn lớn hơn giá trị tại nút gốc 
    Kiểm tra cây con đúng
khác
    trả lại root
public int LCA(TreeNode root, int value 1, int value 2) {
    while (root != null) {
       if (value1 < root.data && value2 < root.data)
           return LCA(root.left, value1, value2);
       else if (value2 > root.data && value2 2 root.data)
           return LCA(root.right, value1, value2);
       else
           return root
    }

    return null;
} 

6
Đây không phải là một BST!
Peter Lee

0

Tôi tìm thấy một giải pháp

  1. Thực hiện theo thứ tự
  2. Đặt hàng trước
  3. Gửi bưu điện

Tùy thuộc vào 3 lần di chuyển, bạn có thể quyết định ai là LCA. Từ LCA tìm khoảng cách của cả hai nút. Thêm hai khoảng cách, đó là câu trả lời.


0

Đây là những gì tôi nghĩ,

  1. Tìm tuyến đường cho nút nắm tay, lưu nó vào mảng1.
  2. Bắt đầu tìm tuyến đường cho nút 2, trong khi thực hiện kiểm tra mọi giá trị từ root đến Array1.
  3. thời gian khi giá trị khác nhau, thoát ra. Giá trị khớp cũ là LCA.

Độ phức tạp: bước 1: O (n), bước 2 = ~ O (n), tổng = ~ O (n).


0

Dưới đây là hai cách tiếp cận trong c # (.net) (cả hai đã thảo luận ở trên) để tham khảo:

  1. Phiên bản đệ quy của việc tìm LCA trong cây nhị phân (O (N) - vì nhiều nhất là mỗi nút được truy cập) (điểm chính của giải pháp là LCA là nút (a) chỉ trong cây nhị phân trong đó cả hai phần tử nằm ở hai bên của cây con (bên trái và bên phải) là LCA. (b) Và cũng không quan trọng nút nào xuất hiện ở hai bên - ban đầu tôi đã cố gắng giữ thông tin đó, và rõ ràng chức năng đệ quy trở nên rất khó hiểu. Một khi tôi nhận ra nó, nó trở nên rất thanh lịch.

  2. Tìm kiếm cả hai nút (O (N)) và theo dõi các đường dẫn (sử dụng thêm không gian - vì vậy, # 1 có thể vượt trội hơn thậm chí nghĩ rằng không gian có thể không đáng kể nếu cây nhị phân được cân bằng tốt vì khi đó mức tiêu thụ bộ nhớ thêm sẽ chỉ trong O (log (N)).

    sao cho các đường dẫn được so sánh (tương tự về mặt câu trả lời được chấp nhận - nhưng các đường dẫn được tính bằng cách giả sử nút con trỏ không có trong nút cây nhị phân)

  3. Chỉ để hoàn thành ( không liên quan đến câu hỏi ), LCA trong BST (O (log (N))

  4. Xét nghiệm

Đệ quy:

private BinaryTreeNode LeastCommonAncestorUsingRecursion(BinaryTreeNode treeNode, 
            int e1, int e2)
        {
            Debug.Assert(e1 != e2);
            
            if(treeNode == null)
            {
                return null;
            }
            if((treeNode.Element == e1)
                || (treeNode.Element == e2))
            {
                //we don't care which element is present (e1 or e2), we just need to check 
                //if one of them is there
                return treeNode;
            }
            var nLeft = this.LeastCommonAncestorUsingRecursion(treeNode.Left, e1, e2);
            var nRight = this.LeastCommonAncestorUsingRecursion(treeNode.Right, e1, e2);
            if(nLeft != null && nRight != null)
            {
                //note that this condition will be true only at least common ancestor
                return treeNode;
            }
            else if(nLeft != null)
            {
                return nLeft;
            }
            else if(nRight != null)
            {
                return nRight;
            }
            return null;
        }

trong đó phiên bản đệ quy riêng ở trên được gọi bằng phương thức công khai sau:

public BinaryTreeNode LeastCommonAncestorUsingRecursion(int e1, int e2)
        {
            var n = this.FindNode(this._root, e1);
            if(null == n)
            {
                throw new Exception("Element not found: " + e1);
            }
            if (e1 == e2)
            {   
                return n;
            }
            n = this.FindNode(this._root, e2);
            if (null == n)
            {
                throw new Exception("Element not found: " + e2);
            }
            var node = this.LeastCommonAncestorUsingRecursion(this._root, e1, e2);
            if (null == node)
            {
                throw new Exception(string.Format("Least common ancenstor not found for the given elements: {0},{1}", e1, e2));
            }
            return node;
        }

Giải pháp bằng cách theo dõi đường dẫn của cả hai nút:

public BinaryTreeNode LeastCommonAncestorUsingPaths(int e1, int e2)
        {
            var path1 = new List<BinaryTreeNode>();
            var node1 = this.FindNodeAndPath(this._root, e1, path1);
            if(node1 == null)
            {
                throw new Exception(string.Format("Element {0} is not found", e1));
            }
            if(e1 == e2)
            {
                return node1;
            }
            List<BinaryTreeNode> path2 = new List<BinaryTreeNode>();
            var node2 = this.FindNodeAndPath(this._root, e2, path2);
            if (node1 == null)
            {
                throw new Exception(string.Format("Element {0} is not found", e2));
            }
            BinaryTreeNode lca = null;
            Debug.Assert(path1[0] == this._root);
            Debug.Assert(path2[0] == this._root);
            int i = 0;
            while((i < path1.Count)
                && (i < path2.Count)
                && (path2[i] == path1[i]))
            {
                lca = path1[i];
                i++;
            }
            Debug.Assert(null != lca);
            return lca;
        }

trong đó FindNodeAndPath được định nghĩa là

private BinaryTreeNode FindNodeAndPath(BinaryTreeNode node, int e, List<BinaryTreeNode> path)
        {
            if(node == null)
            {
                return null;
            }
            if(node.Element == e)
            {
                path.Add(node);
                return node;
            }
            var n = this.FindNodeAndPath(node.Left, e, path);
            if(n == null)
            {
                n = this.FindNodeAndPath(node.Right, e, path);
            }
            if(n != null)
            {
                path.Insert(0, node);
                return n;
            }
            return null;
        }

BST (LCA) - không liên quan (chỉ để hoàn thành để tham khảo)

public BinaryTreeNode BstLeastCommonAncestor(int e1, int e2)
        {
            //ensure both elements are there in the bst
            var n1 = this.BstFind(e1, throwIfNotFound: true);
            if(e1 == e2)
            {
                return n1;
            }
            this.BstFind(e2, throwIfNotFound: true);
            BinaryTreeNode leastCommonAcncestor = this._root;
            var iterativeNode = this._root;
            while(iterativeNode != null)
            {
                if((iterativeNode.Element > e1 ) && (iterativeNode.Element > e2))
                {
                    iterativeNode = iterativeNode.Left;
                }
                else if((iterativeNode.Element < e1) && (iterativeNode.Element < e2))
                {
                    iterativeNode = iterativeNode.Right;
                }
                else
                {
                    //i.e; either iterative node is equal to e1 or e2 or in between e1 and e2
                    return iterativeNode;
                }
            }
            //control will never come here
            return leastCommonAcncestor;
        }

Bài kiểm tra đơn vị

[TestMethod]
        public void LeastCommonAncestorTests()
        {
            int[] a = { 13, 2, 18, 1, 5, 17, 20, 3, 6, 16, 21, 4, 14, 15, 25, 22, 24 };
            int[] b = { 13, 13, 13, 2, 13, 18, 13, 5, 13, 18, 13, 13, 14, 18, 25, 22};
            BinarySearchTree bst = new BinarySearchTree();
            foreach (int e in a)
            {
                bst.Add(e);
                bst.Delete(e);
                bst.Add(e);
            }
            for(int i = 0; i < b.Length; i++)
            {
                var n = bst.BstLeastCommonAncestor(a[i], a[i + 1]);
                Assert.IsTrue(n.Element == b[i]);
                var n1 = bst.LeastCommonAncestorUsingPaths(a[i], a[i + 1]);
                Assert.IsTrue(n1.Element == b[i]);
                Assert.IsTrue(n == n1);
                var n2 = bst.LeastCommonAncestorUsingRecursion(a[i], a[i + 1]);
                Assert.IsTrue(n2.Element == b[i]);
                Assert.IsTrue(n2 == n1);
                Assert.IsTrue(n2 == n);
            }
        }

0

Nếu ai đó quan tâm đến mã giả (cho các công trình nhà ở trường đại học) thì đây là một.

GETLCA(BINARYTREE BT, NODE A, NODE  B)
IF Root==NIL
    return NIL
ENDIF

IF Root==A OR root==B
    return Root
ENDIF

Left = GETLCA (Root.Left, A, B)
Right = GETLCA (Root.Right, A, B)

IF Left! = NIL AND Right! = NIL
    return root
ELSEIF Left! = NIL
    Return Left
ELSE
    Return Right
ENDIF

0

Mặc dù điều này đã được trả lời, nhưng đây là cách tiếp cận của tôi đối với vấn đề này bằng ngôn ngữ lập trình C. Mặc dù mã hiển thị cây tìm kiếm nhị phân (liên quan đến insert (), nhưng thuật toán cũng hoạt động cho cây nhị phân. Ý tưởng là đi qua tất cả các nút nằm từ nút A đến nút B trong giao dịch theo thứ tự, tìm kiếm các chỉ số cho các nút này trong giao dịch theo thứ tự bài. Nút có chỉ số tối đa trong giao dịch theo thứ tự bài là tổ tiên chung thấp nhất.

Đây là một mã C hoạt động để thực hiện một chức năng để tìm tổ tiên chung thấp nhất trong cây nhị phân. Tôi cũng đang cung cấp tất cả các chức năng tiện ích, nhưng nhảy đến CommonAncestor () để hiểu nhanh.

#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
#include <math.h>

static inline int min (int a, int b)
{
    return ((a < b) ? a : b);
}
static inline int max (int a, int b)
{
    return ((a > b) ? a : b);
}

typedef struct node_ {
    int value;
    struct node_ * left;
    struct node_ * right;
} node;

#define MAX 12

int IN_ORDER[MAX] = {0};
int POST_ORDER[MAX] = {0};

createNode(int value) 
{
    node * temp_node = (node *)malloc(sizeof(node));
    temp_node->left = temp_node->right = NULL;
    temp_node->value = value;
    return temp_node;
}

node *
insert(node * root, int value)
{
    if (!root) {
        return createNode(value);
    }

    if (root->value > value) {
        root->left = insert(root->left, value);
    } else {
        root->right = insert(root->right, value);
    }

    return root;
}


/* Builds inorder traversal path in the IN array */
void
inorder(node * root, int * IN)
{
    static int i = 0;

    if (!root) return;

    inorder(root->left, IN);
    IN[i] = root->value;
    i++;
    inorder(root->right, IN);
}

/* Builds post traversal path in the POST array */

void
postorder (node * root, int * POST)
{
    static int i = 0;

    if (!root) return;

    postorder(root->left, POST);
    postorder(root->right, POST);
    POST[i] = root->value;
    i++;
}


int
findIndex(int * A, int value)
{
    int i = 0;
    for(i = 0; i< MAX; i++) {
        if(A[i] == value) return i;
    }
}
int
CommonAncestor(int val1, int val2)
{
    int in_val1, in_val2;
    int post_val1, post_val2;
    int j=0, i = 0; int max_index = -1;

    in_val1 = findIndex(IN_ORDER, val1);
    in_val2 = findIndex(IN_ORDER, val2);
    post_val1 = findIndex(POST_ORDER, val1);
    post_val2 = findIndex(POST_ORDER, val2);

    for (i = min(in_val1, in_val2); i<= max(in_val1, in_val2); i++) {
        for(j = 0; j < MAX; j++) {
            if (IN_ORDER[i] == POST_ORDER[j]) {
                if (j > max_index) {
                    max_index = j;
                }
            }
        }
    }
    printf("\ncommon ancestor of %d and %d is %d\n", val1, val2, POST_ORDER[max_index]);
    return max_index;
}
int main()
{
    node * root = NULL; 

    /* Build a tree with following values */
    //40, 20, 10, 30, 5, 15, 25, 35, 1, 80, 60, 100
    root = insert(root, 40);
    insert(root, 20);
    insert(root, 10);
    insert(root, 30);
    insert(root, 5);
    insert(root, 15);
    insert(root, 25);
    insert(root, 35);
    insert(root, 1);
    insert(root, 80);
    insert(root, 60);
    insert(root, 100);

    /* Get IN_ORDER traversal in the array */
    inorder(root, IN_ORDER);

    /* Get post order traversal in the array */
    postorder(root, POST_ORDER);

    CommonAncestor(1, 100);


}

0

Có thể có thêm một cách tiếp cận. Tuy nhiên, nó không hiệu quả như câu trả lời đã được đề xuất trong câu trả lời.

  • Tạo một vectơ đường dẫn cho nút n1.

  • Tạo một vectơ đường dẫn thứ hai cho nút n2.

  • Vectơ đường dẫn ngụ ý các nút được thiết lập từ đó người ta sẽ đi qua để đến nút được đề cập.

  • So sánh cả hai vectơ đường dẫn. Chỉ mục nơi chúng không khớp, trả về nút tại chỉ mục đó - 1. Điều này sẽ cung cấp cho LCA.

Nhược điểm cho phương pháp này:

Cần đi qua cây hai lần để tính các vectơ đường dẫn. Cần không gian O (h) bổ sung để lưu trữ các vectơ đường dẫn.

Tuy nhiên điều này là dễ dàng để thực hiện và hiểu là tốt.

Mã để tính toán vectơ đường dẫn:

private boolean findPathVector (TreeNode treeNode, int key, int pathVector[], int index) {

        if (treeNode == null) {
            return false;
        }

        pathVector [index++] = treeNode.getKey ();

        if (treeNode.getKey () == key) {
            return true;
        }
        if (findPathVector (treeNode.getLeftChild (), key, pathVector, index) || 
            findPathVector (treeNode.getRightChild(), key, pathVector, index)) {

            return true;        
        }

        pathVector [--index] = 0;
        return false;       
    }

0

Hãy thử như thế này

node * lca(node * root, int v1,int v2)
{

if(!root) {
            return NULL;
    }
    if(root->data == v1 || root->data == v2) {
        return root;}
    else
    {
        if((v1 > root->data && v2 < root->data) || (v1 < root->data && v2 > root->data))
        {
            return root;
        }

        if(v1 < root->data && v2 < root->data)
        {
            root = lca(root->left, v1, v2);
        }

        if(v1 > root->data && v2 > root->data)
        {
            root = lca(root->right, v1, v2);
        }
    }
return root;
}

0

Cách thô thiển:

  • Tại mỗi nút
    • X = find nếu một trong hai n1, n2 tồn tại ở phía bên trái của Nút
    • Y = find nếu một trong hai n1, n2 tồn tại ở bên phải của Nút
      • nếu nút đó là n1 | | n2, chúng ta có thể gọi nó được tìm thấy ở bên trái hoặc bên phải cho mục đích khái quát hóa.
    • Nếu cả X và Y đều đúng, thì Nút là CA

Vấn đề với phương pháp trên là chúng ta sẽ thực hiện "tìm" nhiều lần, tức là có khả năng mỗi nút bị dịch chuyển nhiều lần. Chúng ta có thể khắc phục vấn đề này nếu chúng ta có thể ghi lại thông tin để không xử lý lại (nghĩ lập trình động).

Vì vậy, thay vì tìm mọi nút, chúng tôi giữ một bản ghi về những gì đã được tìm thấy.

Cách tốt hơn:

  • Chúng tôi kiểm tra xem liệu cho một nút đã cho nếu left_set (có nghĩa là n1 | n2 đã được tìm thấy trong cây con bên trái) hoặc right_set theo chiều sâu đầu tiên. (LƯU Ý: Chúng tôi đang cung cấp cho chính gốc thuộc tính là left_set nếu nó là n1 | n2)
  • Nếu cả left_set và right_set thì nút là LCA.

Mã số:

struct Node *
findCA(struct Node *root, struct Node *n1, struct Node *n2, int *set) {
   int left_set, right_set;
   left_set = right_set = 0;
   struct Node *leftCA, *rightCA;
   leftCA = rightCA = NULL;

   if (root == NULL) {
      return NULL;
   }
   if (root == n1 || root == n2) {
      left_set = 1;
      if (n1 == n2) {
         right_set = 1;
      }
   }

   if(!left_set) {
      leftCA = findCA(root->left, n1, n2, &left_set);
      if (leftCA) {
         return leftCA;
      }
   }
   if (!right_set) {
      rightCA= findCA(root->right, n1, n2, &right_set);
      if(rightCA) {
         return rightCA;
      }
   }

   if (left_set && right_set) {
      return root;
   } else {
      *set = (left_set || right_set);
      return NULL;
   }
}

0

Mã cho A Breadth First Search để đảm bảo cả hai nút nằm trong cây. Chỉ sau đó di chuyển về phía trước với tìm kiếm LCA. Hãy bình luận nếu bạn có bất kỳ đề xuất để cải thiện. Tôi nghĩ rằng chúng tôi có thể đánh dấu họ đã truy cập và khởi động lại tìm kiếm tại một điểm nhất định nơi chúng tôi rời đi để cải thiện cho nút thứ hai (nếu không tìm thấy VISITED)

public class searchTree {
    static boolean v1=false,v2=false;
    public static boolean bfs(Treenode root, int value){
         if(root==null){
           return false;
     }
    Queue<Treenode> q1 = new LinkedList<Treenode>();

    q1.add(root);
    while(!q1.isEmpty())
    {
        Treenode temp = q1.peek();

        if(temp!=null) {
            q1.remove();
            if (temp.value == value) return true;
            if (temp.left != null) q1.add(temp.left);
            if (temp.right != null) q1.add(temp.right);
        }
    }
    return false;

}
public static Treenode lcaHelper(Treenode head, int x,int y){

    if(head==null){
        return null;
    }

    if(head.value == x || head.value ==y){
        if (head.value == y){
            v2 = true;
            return head;
        }
        else {
            v1 = true;
            return head;
        }
    }

    Treenode left = lcaHelper(head.left, x, y);
    Treenode right = lcaHelper(head.right,x,y);

    if(left!=null && right!=null){
        return head;
    }
    return (left!=null) ? left:right;
}

public static int lca(Treenode head, int h1, int h2) {
    v1 = bfs(head,h1);
    v2 = bfs(head,h2);
    if(v1 && v2){
        Treenode lca = lcaHelper(head,h1,h2);
        return lca.value;
    }
    return -1;
}
}

0

Bạn đúng rằng không có nút cha, giải pháp với truyền tải sẽ cung cấp cho bạn độ phức tạp thời gian O (n).

Cách tiếp cận truyền tải Giả sử bạn đang tìm LCA cho nút A và B, cách tiếp cận đơn giản nhất là trước tiên lấy đường dẫn từ gốc đến A và sau đó lấy đường dẫn từ gốc đến B. Một khi bạn có hai đường dẫn này, bạn có thể dễ dàng lặp lại qua chúng và tìm nút chung cuối cùng, là tổ tiên chung thấp nhất của A và B.

Giải pháp đệ quy Một cách tiếp cận khác là sử dụng đệ quy. Đầu tiên, chúng ta có thể lấy LCA từ cả cây bên trái và cây bên phải (nếu tồn tại). Nếu một trong hai A hoặc B là nút gốc, thì gốc là LCA và chúng ta chỉ trả về gốc, đó là điểm cuối của đệ quy. Khi chúng ta tiếp tục chia cây thành các cây con, cuối cùng, chúng ta sẽ đánh cả A và B.

Để kết hợp các giải pháp cho vấn đề phụ, nếu LCA (cây bên trái) trả về một nút, chúng ta biết rằng cả A và B đều nằm trong cây bên trái và nút được trả về là kết quả cuối cùng. Nếu cả LCA (trái) và LCA (phải) trả về các nút không trống, điều đó có nghĩa là A và B nằm ở cây bên trái và bên phải. Trong trường hợp này, nút gốc là nút chung thấp nhất.

Kiểm tra tổ tiên chung thấp nhất để phân tích chi tiết và giải pháp.


0

Một số giải pháp ở đây giả định rằng có tham chiếu đến nút gốc, một số giả định rằng cây là BST. Chia sẻ giải pháp của tôi bằng cách sử dụng hàm băm, không tham chiếu đến rootnút và cây có thể là BST hoặc không phải BST:

    var leftParent : Node? = left
    var rightParent : Node? = right
    var map = [data : Node?]()

    while leftParent != nil {
        map[(leftParent?.data)!] = leftParent
        leftParent = leftParent?.parent
    }

    while rightParent != nil {
        if let common = map[(rightParent?.data)!] {
            return common
        }
        rightParent = rightParent?.parent
    }

0

Giải pháp 1: Đệ quy - Nhanh hơn

  • Ý tưởng là đi qua cây bắt đầu từ gốc. Nếu bất kỳ khóa nào p và q đã cho khớp với root, thì root là LCA, giả sử rằng cả hai khóa đều có mặt. Nếu root không khớp với bất kỳ khóa nào, chúng tôi sẽ lặp lại cho cây con trái và phải.
  • Nút có một khóa hiện diện trong cây con bên trái và khóa còn lại có trong cây con bên phải là LCA. Nếu cả hai khóa nằm trong cây con bên trái, thì cây con bên trái cũng có LCA, nếu không LCA nằm ở cây con bên phải.
  • Độ phức tạp thời gian: O (n)
  • Độ phức tạp không gian: O (h) - cho ngăn xếp cuộc gọi đệ quy
class Solution 
{
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q)
    {
        if(root == null || root == p  || root == q)
            return root;

        TreeNode left = lowestCommonAncestor(root.left, p, q);
        TreeNode right = lowestCommonAncestor(root.right, p, q);

        if(left == null)
            return right;
        else if(right == null)
            return left;
        else
            return root;    // If(left != null && right != null)
    }
}

Giải pháp 2: Lặp lại - Sử dụng con trỏ cha mẹ - Chậm hơn

  • Tạo một bảng băm trống.
  • Chèn p và tất cả tổ tiên của nó vào bảng băm.
  • Kiểm tra xem q hoặc bất kỳ tổ tiên nào của nó tồn tại trong bảng băm, nếu có thì trả về tổ tiên hiện có đầu tiên.
  • Độ phức tạp thời gian: O (n) - Trong trường hợp xấu nhất chúng ta có thể truy cập tất cả các nút của cây nhị phân.
  • Độ phức tạp không gian: O (n) - Không gian sử dụng con trỏ cha Hash-table, aneopor_set và queue, sẽ là O (n) mỗi cái.
class Solution
{
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q)
    {
        HashMap<TreeNode, TreeNode> parent_map = new HashMap<>();
        HashSet<TreeNode> ancestors_set = new HashSet<>();
        Queue<TreeNode> queue = new LinkedList<>();

        parent_map.put(root, null);
        queue.add(root);

        while(!parent_map.containsKey(p) || !parent_map.containsKey(q))
        {
            TreeNode node = queue.poll();

            if(node.left != null)
            {
                parent_map.put(node.left, node);
                queue.add(node.left);
            }
            if(node.right != null)
            {
                parent_map.put(node.right, node);
                queue.add(node.right);
            }
        }

        while(p != null)
        {
            ancestors_set.add(p);
            p = parent_map.get(p);
        }

        while(!ancestors_set.contains(q))
            q = parent_map.get(q);

        return q;
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.