一、問題
求有根樹的任意兩個節點的最近公共祖先(一般來說都是指二叉樹)。最近公共祖先簡稱LCA(Lowest Common Ancestor)。例如,如下圖一棵普通的二叉樹。
結點3和結點4的最近公共祖先是結點2,即LCA(3,4)=2 。在此,需要注意到當兩個結點在同一棵子樹上的情況,如結點3和結點2的最近公共祖先為2,即 LCA(3,2)=2。同理:LCA(5,6)=4,LCA(6,10)=1。
明確了題意,咱們便來試着解決這個問題。直觀的做法,可能是針對是否為二叉查找樹分情況討論,這也是一般人最先想到的思路。除此之外,還有所謂的Tarjan算法、倍增算法、以及轉換為RMQ問題(求某段區間的極值)。后面這幾種算法相對高級,不那么直觀,但思路比較有啟發性,留作以后了解一下也有裨益。
二、思路
解法一:暴力解法,在有parent指針的情況下,對兩個節點依次向上回溯,直到兩個節點相同,那么這個節點就是最近公共祖先。時間復雜度為O(n²)
解法二:鏈表的交叉,在有parent指針的情況下,對兩個節點分別到根節點的路徑上的節點形成兩個鏈表,因為兩個鏈表很大可能不一樣長,然后我們可以對其中長的一個鏈表從開頭進行裁剪,形成兩個鏈表長度一樣,然后遍歷直到相等,雖說優化了一點,但本質上還是暴力破解。
解法三:借用數組和列表,在沒有parent指針的情況,我們只能從根節點往下遍歷,而不能進行往上回溯。所以可以借用數組或列表來保存數據,后面進行比對。和鏈表的交叉差不多。
解法四:不借用額外的數據結構,沒有parent指針。大概思路呢就是如果兩個節點分屬在根節點的兩邊,返回根節點,如果兩個節點同在左子樹或右子樹,遞歸求解。
解法五:這種解法現在不太懂,現在留作記錄以后觀看。其中解法四和解法五在代碼中體現。
三、代碼

1 import java.util.ArrayList; 2 import java.util.List; 3 4 public class LCA { 5 public int getLCA(int a, int b) { 6 TreeNode<Integer> root = of(10); 7 8 TreeNode<Integer> lca = getLCA2(root, new TreeNode<Integer>(a), new TreeNode<Integer>(b)); 9 return lca == null ? -1 : lca.val; 10 } 11 12 //=====解法四=========== 13 // 看兩個節點是否在同一側 14 private TreeNode<Integer> getLCA(TreeNode<Integer> root, TreeNode<Integer> p, TreeNode<Integer> q) { 15 if (root == null) 16 return null; 17 if (root.equals(p) || root.equals(q)) 18 return root; 19 20 boolean is_p_on_left = cover(root.left, p); 21 boolean is_q_on_right = cover(root.right, q); 22 if (is_p_on_left == is_q_on_right) {// 在root的兩端 23 return root; 24 } else if (is_p_on_left) {// 在root的左端 25 return getLCA(root.left, p, q); 26 } else { 27 return getLCA(root.right, p, q); 28 } 29 } 30 31 // 解法五 32 // 很難理解 遞歸定義不明確 第一次看到 33 private TreeNode<Integer> getLCA2(TreeNode<Integer> root, TreeNode<Integer> p, TreeNode<Integer> q) { 34 if (root == null) 35 return null; 36 if (root.equals(p) && root.equals(q)) 37 return root; 38 39 // x是lca,或者是p(p在這一側),或者是q(q在這一側),或者是null(pq都不在這一側) 40 TreeNode<Integer> x = getLCA2(root.left, p, q); 41 if (x != null && !x.equals(p) && !x.equals(q)) {// 在左子樹找到了lca 42 return x; 43 } 44 45 TreeNode<Integer> y = getLCA2(root.right, p, q); 46 if (y != null && !y.equals(p) && !y.equals(q)) {// 在右子樹找到了lca 47 return y; 48 } 49 50 // x:p,q,null y :q,p,null 51 if (x != null && y != null) {// 一邊找着一個 52 return root; 53 } else if (root.equals(p) || root.equals(q)) { 54 return root; 55 } else { 56 return x == null ? y : x;// 有一個不為null,則返回,都為null,返回null 57 } 58 } 59 60 /** 61 * 判斷x節點是否在n所代表的子樹中 62 * 63 * @param n 64 * @param x 65 * @return 66 */ 67 private boolean cover(TreeNode<Integer> n, TreeNode<Integer> x) { 68 if (n == null) 69 return false; 70 if (n.equals(x)) 71 return true; 72 return cover(n.left, x) || cover(n.right, x); 73 } 74 75 public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { 76 // 發現目標節點則通過返回值標記該子樹發現了某個目標結點 77 if (root == null || root.equals(p.val) || root.equals(q)) 78 return root; 79 // 查看左子樹中是否有目標結點,沒有為null 80 TreeNode left = lowestCommonAncestor(root.left, p, q); 81 // 查看右子樹是否有目標節點,沒有為null 82 TreeNode right = lowestCommonAncestor(root.right, p, q); 83 // 都不為空,說明左右子樹都有目標結點,則公共祖先就是本身 84 if (left != null && right != null) 85 return root; 86 // 如果發現了目標節點,則繼續向上標記為該目標節點 87 return left == null ? right : left; 88 } 89 90 static TreeNode<Integer> of(int n) { 91 List<TreeNode<Integer>> list = new ArrayList<TreeNode<Integer>>(); 92 for (int i = 0; i < n; i++) { 93 list.add(new TreeNode<Integer>(i + 1)); 94 } 95 for (int i = 0; i < n; i++) { 96 TreeNode<Integer> parent = list.get(i); 97 if (i * 2 + 1 < n) { 98 TreeNode<Integer> left = list.get(i * 2 + 1); 99 parent.left = left; 100 left.parent = parent; 101 } else 102 break; 103 if (i * 2 + 2 < n) { 104 TreeNode<Integer> right = list.get(i * 2 + 2); 105 parent.right = right; 106 right.parent = parent; 107 } 108 } 109 return list.get(0); 110 } 111 112 private static class TreeNode<T> { 113 public T val; 114 public TreeNode<T> left = null; 115 public TreeNode<T> right = null; 116 TreeNode<T> parent; 117 118 public TreeNode(T val) { 119 this.val = val; 120 } 121 122 @Override 123 public boolean equals(Object o) { 124 if (this == o) 125 return true; 126 if (o == null || getClass() != o.getClass()) 127 return false; 128 129 TreeNode<?> treeNode = (TreeNode<?>) o; 130 131 return val != null ? val.equals(treeNode.val) : treeNode.val == null; 132 } 133 134 @Override 135 public int hashCode() { 136 return val != null ? val.hashCode() : 0; 137 } 138 } 139 }