文章目錄
- 二叉搜索樹的簡介
- [98. 驗證二叉搜索樹](https://leetcode-cn.com/problems/validate-binary-search-tree/)
- [700. 二叉搜索樹中的搜索](https://leetcode-cn.com/problems/search-in-a-binary-search-tree/)
- [701. 二叉搜索樹中的插入操作](https://leetcode-cn.com/problems/insert-into-a-binary-search-tree/)
- [450. 刪除二叉搜索樹中的節點](https://leetcode-cn.com/problems/delete-node-in-a-bst/)
- [面試題54. 二叉搜索樹的第k大節點](https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-di-kda-jie-dian-lcof/)
- [938. 二叉搜索樹的範圍和](https://leetcode-cn.com/problems/range-sum-of-bst/)
二叉搜索樹的簡介
二叉搜索樹是這樣的二叉樹:任意一個節點,其左兒子節點小於它,右兒子節點大於它
4
/ \
2 7
/ \
1 3
因此,二叉搜索樹的具有非常高效的查找、插入和刪除操作。
且,二叉搜索樹的中序遍歷,是一個遞增的序列
98. 驗證二叉搜索樹
給定一個二叉樹,判斷其是否是一個有效的二叉搜索樹。
假設一個二叉搜索樹具有如下特徵:
- 節點的左子樹只包含小於當前節點的數。
- 節點的右子樹只包含大於當前節點的數。
- 所有左子樹和右子樹自身必須也是二叉搜索樹。
示例 1:
輸入:
2
/ \
1 3
輸出: true
示例 2:
輸入:
5
/ \
1 4
/ \
3 6
輸出: false
解釋: 輸入爲: [5,1,4,null,null,3,6]。
根節點的值爲 5 ,但是其右子節點值爲 4 。
分析:每個節點,需要大於左兒子節點的值,小於右兒子節點的值。且其左兒子和右兒子都是符合二叉搜索樹的,很顯然可以用遞歸解決。需要注意的是,右子樹的所有節點的值必須都大於根節點,左子樹的所有結點的值都必須小於根節點,因此需要設置一個輔助函數來限制範圍。
- 空則true
- 該結點的值沒有在限制的範圍內,則返回false
- 否則返回兩個子樹的helper,需要更新子樹的限制範圍
class Solution {
public:
bool helper(TreeNode* root, long long lower, long long upper) {
if (root == NULL) return true; //爲空返回true
if (root -> val <= lower || root -> val >= upper) return false;
return helper(root -> left, lower, root -> val) && helper(root -> right, root -> val, upper);
}
bool isValidBST(TreeNode* root) {
return helper(root, LONG_MIN, LONG_MAX); //根沒有限制
}
};
700. 二叉搜索樹中的搜索
給定二叉搜索樹(BST)的根節點和一個值。 你需要在BST中找到節點值等於給定值的節點。 返回以該節點爲根的子樹。 如果節點不存在,則返回 NULL。
例如,
給定二叉搜索樹:
4
/ \
2 7
/ \
1 3
值: 2
你應該返回如下子樹:
2
/ \
1 3
在上述示例中,如果要找的值是 5,但因爲沒有節點值爲 5,我們應該返回 NULL。
分析:二叉搜索樹的搜索是最基礎的應用,如果要搜的結點值小於當前結點,則向左走,否則向右走,直至找到或者爲空。
//遞歸方式
class Solution {
public:
TreeNode* searchBST(TreeNode* root, int val)
{
if(root==NULL) return NULL;
if(root->val==val)
return root;
else if(root->val<val)
return searchBST(root->right,val);
else
return searchBST(root->left,val);
}
};
//非遞歸方式
class Solution {
public:
TreeNode* searchBST(TreeNode* root, int val)
{
if(root==NULL) return NULL;
TreeNode* temp=root;
while(temp!=NULL)
{
if(temp->val==val) return temp;
else if(temp->val<val) temp=temp->right;
else temp=temp->left;
}
return NULL;
}
};
701. 二叉搜索樹中的插入操作
給定二叉搜索樹(BST)的根節點和要插入樹中的值,將值插入二叉搜索樹。 返回插入後二叉搜索樹的根節點。 保證原始二叉搜索樹中不存在新值。
注意,可能存在多種有效的插入方式,只要樹在插入後仍保持爲二叉搜索樹即可。 你可以返回任意有效的結果。
例如,
給定二叉搜索樹:
4
/ \
2 7
/ \
1 3
和 插入的值: 5
你可以返回這個二叉搜索樹:
4
/ \
2 7
/ \ /
1 3 5
分析:二叉搜索樹具有良好的順序。二叉搜索樹的巨大優勢就是:在平均情況下,能夠在 的時間內完成搜索和插入元素。
因此等價於找到要插入的值對應的位置即可,將插入的節點作爲葉子節點的子節點插入。首先建立相應的結點,然後搜索,找到正確的位置然後插入。
- 若
val > node.val
,插入到右子樹。 - 若
val < node.val
,插入到左子樹。
//遞歸寫法:
class Solution {
public:
TreeNode* insertIntoBST(TreeNode* root, int val)
{
TreeNode* new_node=new TreeNode(val);
if(root==NULL) return new_node;
else if(root->val<val) root->right=insertIntoBST(root->right,val);
else root->left=insertIntoBST(root->left,val);
return root;
}
};
//非遞歸寫法:
class Solution {
public:
TreeNode* insertIntoBST(TreeNode* root, int val)
{
TreeNode* new_node=new TreeNode(val);
if(root==NULL) return new_node;
TreeNode* pre=NULL,*temp=root; //找到插入位置,然後連接起來
while(temp!=NULL)
{
pre=temp;
if(temp->val>val) temp=temp->left;
else temp=temp->right;
}
//則temp位置就是要插入的位置,pre爲其父結點
if(pre->val<val)
pre->right=new_node;
else
pre->left=new_node;
return root;
}
};
450. 刪除二叉搜索樹中的節點
給定一個二叉搜索樹的根節點 root 和一個值 key,刪除二叉搜索樹中的 key 對應的節點,並保證二叉搜索樹的性質不變。返回二叉搜索樹(有可能被更新)的根節點的引用。
一般來說,刪除節點可分爲兩個步驟:
首先找到需要刪除的節點;如果找到了,刪除它。
說明: 要求算法時間複雜度爲 O(h),h 爲樹的高度。
示例:
root = [5,3,6,2,4,null,7]
key = 3
5
/ \
3 6
/ \ \
2 4 7
給定需要刪除的節點值是 3,所以我們首先找到 3 這個節點,然後刪除它。
一個正確的答案是 [5,4,6,2,null,null,7], 如下圖所示。
5
/ \
4 6
/ \
2 7
另一個正確答案是 [5,2,6,null,4,null,7]。
5
/ \
2 6
\ \
4 7
分析:二叉搜索樹的刪除操作較爲複雜,主要步驟爲:找到要刪除的節點,刪除該節點(保持二叉樹的特性)
一種可行的刪除操作爲:
-
如果刪除的要刪除的節點是葉節點,則將其刪去即可
-
需要刪除的節點沒有左兒子,則將右兒子提上去即可
-
需要刪除的節點的左兒子沒有右兒子,則將左兒子提上去即可
-
以上情況均不滿足,則將左兒子的子孫中最大的節點提到刪除的節點處
class Solution {
public:
TreeNode* deleteNode(TreeNode* root, int key) {
if (root==NULL) return root;
//--------------找到要刪除的節點----------
if (root->val < key)
{
root->right = deleteNode(root->right, key);
return root;
}
if (root->val > key)
{
root->left = deleteNode(root->left, key);
return root;
}
// 此時的root就是要刪除的節點
if (root->left==NULL) //root的left爲空,則將右子樹替代root
{
TreeNode* tmp = root->right;
delete root;
return tmp;
}
if (root->right==NULL) //root的right爲空,則將左子樹替代root
{
TreeNode* tmp = root->left;
delete root;
return tmp;
}
//都不是空的話,找到左子樹中的最大值放入
TreeNode* tmp = root->left;
if(tmp->right==NULL) //如果左子樹沒有右節點,將左子樹放入即可
{
tmp->right=root->right;
delete root;
return tmp;
}
//否則找到左子樹中最大的節點
TreeNode* pre=root;
while (tmp->right!=NULL)
{
pre=tmp;
tmp = tmp->right; //找到左子樹的最大值
}
//將最大節點的值傳入root,將pre->right指向tmp的left即可
root->val=tmp->val;
pre->right=tmp->left;
return root;
}
};
面試題54. 二叉搜索樹的第k大節點
給定一棵二叉搜索樹,請找出其中第k大的節點。
示例 1:
輸入: root = [3,1,4,null,2], k = 1
3
/ \
1 4
\
2
輸出: 4
示例 2:
輸入: root = [5,3,6,2,4,null,null,1], k = 3
5
/ \
3 6
/ \
2 4
/
1
輸出: 4
分析:二叉搜索樹的中序遍歷是遞增數列,因此,中序遍歷的逆數列是一個遞減數列。可以用一個值記錄已經遍歷多少個節點,則遍歷到第k個時即爲所求。
如何獲取中序遍歷倒序數列?改變遍歷順序即可
- 先以該方式遍歷右子樹
- 再遍歷根節點
- 再以該方式遍歷左子樹
class Solution
{ private:
int n = 0; //記錄次數
int res = 0; //記錄結果
public:
int kthLargest(TreeNode *root, int k)
{
n = k;
helper(root);
return res;
}
void helper(TreeNode *root){
if(root->right != NULL && n>0 )
helper(root->right);
n--;
if(n==0)
{
res = root->val;
return;
}
if(root->left != NULL && n>0 ) helper(root->left);
}
};
938. 二叉搜索樹的範圍和
給定二叉搜索樹的根結點 root
,返回 L
和 R
(含)之間的所有結點的值的和。
二叉搜索樹保證具有唯一的值。
示例 1:
輸入:root = [10,5,15,3,7,null,18], L = 7, R = 15
輸出:32
示例 2:
輸入:root = [10,5,15,3,7,13,18,1,null,6], L = 6, R = 10
輸出:23
分析:找到 L R 範圍內的節點之和,因此可採用遞歸的方式
-
如果當前節點是NULL,返回0即可
-
如果當前節點的值小於L,說明 L-R範圍內的樹都在右子樹,返回 右子樹在該範圍的和即可
-
如果當前節點的值大於R,說明 L-R範圍內的樹都在左子樹,返回 左子樹在該範圍的和即可
-
否則,說明當前節點的值在 L和R之間,返回當前節點的值加上左子樹、右子樹在該範圍的和即可
(因爲二叉搜索樹的排序性,因此不需要更新L,R)
class Solution {
public:
int rangeSumBST(TreeNode* root, int L, int R)
{
if(root==NULL) return 0;
if(root->val<L) return rangeSumBST(root->right,L,R);
else if(root->val>R) return rangeSumBST(root->left,L,R);
else return root->val+rangeSumBST(root->right,L,R)+rangeSumBST(root->left,L,R);
}
};