LeetCode-二叉樹的最近公共祖先
二叉樹的最近公共祖先
給定一個二叉樹, 找到該樹中兩個指定節點的最近公共祖先。
遇到任何遞迴型的問題,無非就是靈魂三問:
1、這個函式是幹嘛的?
情況 1,如果
p
和q
都在以root
為根的樹中,函式返回的即使p
和q
的最近公共祖先節點。情況 2,那如果
p
和q
都不在以root
為根的樹中怎麼辦呢?函式理所當然地返回null
唄。情況 3,那如果
p
和q
只有一個存在於root
為根的樹中呢?函式就會返回那個節點。
2、這個函式引數中的變數是什麼的是什麼?
函式引數中的變數是
root
,因為根據框架,lowestCommonAncestor(root)
會遞迴呼叫root.left
和root.right
;至於p
和q
,我們要求它倆的公共祖先,它倆肯定不會變化的。把「以
root
為根」轉移成「以root
的子節點為根」,不斷縮小問題規模
3、得到函式的遞迴結果,你應該幹什麼?
先想 base case,如果
root
為空,肯定得返回null
。如果root
本身就是p
或者q
,比如說root
就是p
節點吧,如果q
存在於以root
為根的樹中,顯然root
就是最近公共祖先;即使q
不存在於以root
為根的樹中,按照情況 3 的定義,也應該返回root
節點。
以上兩種情況的 base case 就可以把框架程式碼填充一點了:
TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { // 兩種情況的 base case if (root == null) return null; if (root == p || root == q) return root; TreeNode left = lowestCommonAncestor(root.left, p, q); TreeNode right = lowestCommonAncestor(root.right, p, q); }
用遞迴呼叫的結果left
和right
來搞點事情。根據剛才第一個問題中對函式的定義,我們繼續分情況討論:
情況 1,如果
p
和q
都在以root
為根的樹中,那麼left
和right
一定分別是p
和q
(從 base case 看出來的)。情況 2,如果
p
和q
都不在以root
為根的樹中,直接返回null
。情況 3,如果
p
和q
只有一個存在於root
為根的樹中,函式返回該節點。
left
和right
非空,分別是p
和q
,可以說明root
是它們的公共祖先,但能確定root
就是「最近」公共祖先嗎?
這就是一個巧妙的地方了,因為這裡是二叉樹的後序遍歷啊!前序遍歷可以理解為是從上往下,而後序遍歷是從下往上,就好比從
p
和q
出發往上走,第一次相交的節點就是這個root
,所以這個root
當然是最近公共祖先了
public class LowestCommonAncestor {
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) { val = x; }
}
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
// base case
if(root == null) {
return null;
}
// 如果遍歷到的根節點為p或者q,說明在父節點的子樹下找到了該節點,所以返回該父節點
if(p==root||q==root) {
return root;
}
// 後序遍歷,遞迴呼叫,left是在父節點的左子樹中找到的p節點或者q節點或者空節點,right同理
TreeNode left = lowestCommonAncestor(root.left,p,q);
TreeNode right = lowestCommonAncestor(root.right,p,q);
// 如果左子樹和右子樹都找到了p,q節點,說明父節點就是最近公共祖先,因為是後序遍歷,所以能確保該父節點就是最近的祖先
if(left!=null&&right!=null) {
return root;
}
// 如果左子樹和右子樹都沒有找到p,q節點,說明p,q節點和該父節點沒關係,返回null,表示該父節點的子樹下不包含p,q節點
if(left==null&&right==null) {
return null;
}
// 在上邊條件都不滿足的情況下,說明在該父節點下只找到了p或者q其中一個節點,返回這個找到的節點即可,
// 如果是最外層的遞迴呼叫可能是因為另一個節點在這個節點的子樹下,已經遍歷找到了這個節點,就直接返回了沒有再去找另一個節點,或者是兩個節點都在左子樹的某一個父節點下,所以右子樹沒有找到節點,這裡當然返回這兩個節點所在樹的子呼叫裡的父節點也就是現在返回的left了,右子樹同理
return left!=null?left:right;
}
}