查詢二叉樹子節點的共同父節點
技術標籤:iOS 開發
查詢二叉樹子節點的共同父節點
給定一個二叉搜尋樹, 找到該樹中兩個指定節點的最近公共祖先。
百度百科中最近公共祖先的定義為:“對於有根樹 T 的兩個結點 p、q,最近公共祖先表示為一個結點 x,滿足 x 是 p、q 的祖先且 x 的深度儘可能大(一個節點也可以是它自己的祖先)。”
例項1
輸入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
輸出: 6
解釋: 節點 2 和節點 8 的最近公共祖先是 6。
例項2
輸入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4
輸出: 2
解釋: 節點 2 和節點 4 的最近公共祖先是 2, 因為根據定義最近公共祖先節點可以為節點本身。
說明:
- 所有節點的值都是唯一的。
- p、q 為不同節點且均存在於給定的二叉搜尋樹中。
分析
對於二叉樹來講,由於左右子樹指標的存在,使得正常情況下的自上而下遍歷顯得比較簡單,而下而上的查詢並不那麼容易,所以一種直觀的思維就是從根節點開始遍歷,直到找到節點
p
p
p,記錄路徑陣列為
p
a
t
h
_
p
path\_p
path_p,同理找到根節點到節點
q
q
q的路徑陣列
p
a
t
h
_
q
path\_q
path_q,只要能夠找到兩個路徑組中最到的
i
n
d
e
x
index
實現
基於上述思考,嘗試使用陣列來進行路徑儲存。
struct Path {
int capacity; //容量
int occupy; // 實際佔用量
struct TreeNode **result;// 儲存的路徑節點
};
void addElement(struct Path *path, struct TreeNode* node) {
if (path->occupy == path->capacity) {
// 擴容
path->capacity *= 2;
path->result = realloc(path->result, sizeof(struct TreeNode *) * path->capacity);
}
path->result[path->occupy++] = node;
}
struct TreeNode* lowestCommonAncestor(struct TreeNode* root, struct TreeNode* p, struct TreeNode* q){
struct Path *path_p = malloc(sizeof(struct Path));
path_p->capacity = 50;
path_p->occupy = 0;
int size = sizeof(struct TreeNode *) * path_p->capacity;
path_p->result = malloc(size);
memset(path_p->result, 0 , size);
struct TreeNode *current = root;
while(current->val != p->val) {
addElement(path_p, current);
if (current->val > p->val) {
current = current -> left;
} else {
current = current -> right;
}
}
addElement(path_p, current);
current = root;
struct Path *path_q = malloc(sizeof(struct Path));
path_q->capacity = 50;
path_q->occupy = 0;
size = sizeof(struct TreeNode *) * path_q->capacity;
path_q->result = malloc(size);
memset(path_q->result, 0 , size);
while(current->val != q->val) {
addElement(path_q, current);
if (current->val > q->val) {
current = current -> left;
} else {
current = current -> right;
}
}
addElement(path_q, current);
struct TreeNode *node = NULL;
for(int pIndex = path_p->occupy - 1; pIndex >= 0; pIndex--) {
for(int qIndex = path_q->occupy - 1; qIndex >= 0; qIndex--) {
if(path_p->result[pIndex] == path_q->result[qIndex]) {
return path_p->result[pIndex];
}
}
}
return NULL;
}
演算法複雜度
- 時間複雜度:最壞的情況下,二叉搜尋樹變成了一個類似於連結串列的結構,而 p , q p,q p,q是在最底端的兩個節點那麼搜尋 p , q p,q p,q節點的時間複雜度都可以達到 n n n( n n n為樹中節點個數),時間複雜度為 O ( n ) O(n) O(n);
- 空間複雜度:同樣最壞的情況下,需要使用開闢跟節點數相同的陣列空間來儲存節點路徑,所以空間複雜度也為 O ( n ) O(n) O(n).
其他演算法
對於上述演算法來講需要遍歷兩次樹結構來獲取跟節點到指定節點的路徑,然後倒敘獲取路徑陣列中第一個相同節點即可最近父節點.但事實上,可以嘗試將兩次查詢合併在一起,對於當前節點 c u r r e n t current current,無非有三種情況:
- current->val > p->val && current->val > q->val,則說明p,q均在current的左子樹上,此時取current = current->left;
- current->val < p->val && current->val < q->val,則說明p,q均在current的右子樹上,此時取current = current->right;
- 最後一種情況,要麼current就是p或者q節點之一,要麼p,q分別在current的左右子樹上.也就是要查詢的最近父節點。實現起來發給這個樣子:
struct TreeNode* lowestCommonAncestor(struct TreeNode* root, struct TreeNode* p, struct TreeNode* q) {
struct TreeNode* ancestor = root;
while (true) {
if (p->val < ancestor->val && q->val < ancestor->val) {
ancestor = ancestor->left;
} else if (p->val > ancestor->val && q->val > ancestor->val) {
ancestor = ancestor->right;
} else {
return ancestor
}
}
return NULL;
}
這樣就可以將時間複雜度降低為 O ( n ) O(n) O(n),同時空間複雜度也將為常數 O ( 1 ) O(1) O(1).
題目升級
如果題目中的樹只是一顆普通的二叉樹,那麼最近父節點該怎麼查詢?
其實嘗試將結果分類,會發現無外乎以下情況:
- p,q結點分佈在當前結點兩側或者當前結點就是p或者q之一,那麼根結點就是最近父節點;
- p,q結點在當前結點的左子樹上,那麼最近父結點肯定是第一個查詢到的p或者q;
- p,q結點分佈在當前結點右子樹上,那麼那麼最近父結點肯定是第一個查詢到的p或者q;
這樣就可以使用遞迴進行查詢:
struct TreeNode* lowestCommonAncestor(struct TreeNode* root, struct TreeNode* p, struct TreeNode* q) {
if (!root) return NULL;
if (root->val == p->val || root->val == q->val ) return root;
struct TreeNode *left = lowestCommonAncestor(root->left, p, q);
struct TreeNode *right = lowestCommonAncestor(root->right, p, q);
if (left && right) return root;
return left ? left : right;
}
同樣最壞的情況是,二叉樹退化成了一個類似於單鏈表的結構,p,q兩個節點就在表的末端最後兩個節點,這樣的話,時間複雜度也會變為 O ( n ) O(n) O(n);不消耗額外的空間。