【LeetCode】12.二叉树系列——搜索树

总目录:

LeetCode系列导航目录

 

0.理论基础

0.1.二叉搜索树Bineary Search Tree

二叉搜索树是一个有序树:

(1)若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;

(2)若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;

(3)它的左、右子树也分别为二叉搜索树;

0.2.搜索方式

二叉搜索树的中序遍历集合是递增序列,搜索特定值就是二分法不断分流左右

0.3.代码实例

333

 

1.二叉搜索树中的搜索

1.1.问题描述

给定二叉搜索树(BST)的根节点 root 和一个整数值 val。
你需要在 BST 中找到节点值等于 val 的节点。 返回以该节点为根的子树。 如果节点不存在,则返回 null 。
链接:https://leetcode.cn/problems/search-in-a-binary-search-tree

1.2.要点

搜索过程中二分、左右路径选择

1.3.代码实例

递归,前序遍历

 1 class Solution {
 2 public:
 3     TreeNode* searchBST(TreeNode* root, int val) {
 4         if(root==NULL){
 5             return NULL;
 6         }
 7 
 8         TreeNode* tgtNode=NULL;
 9         if(root->val==val){
10             tgtNode=root;
11         }
12         else if(root->val>val){
13             tgtNode=searchBST(root->left,val);
14         }
15         else if(root->val<val){
16             tgtNode=searchBST(root->right,val);
17         }
18         
19         return tgtNode;
20     }
21 };
View Code

 

2.验证是否是二叉搜索树

2.1.问题描述

给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。
链接:https://leetcode.cn/problems/validate-binary-search-tree

2.2.要点

根据定义,并不是节点大于其左子节点、小于其右子节点就可以,而是要大于整个左子树、小于整个右子树。

按中序遍历的方法来看,后遇到的节点必须大于前面遇到的节点

1递归+中序遍历

2.3.代码实例

递归+中序遍历

 1 class Solution {
 2 public:
 3     long preVal=LONG_MIN;//防溢出
 4     bool isValidBST(TreeNode* root) {
 5         if(root==NULL){
 6             return true;
 7         }
 8         
 9         if(!isValidBST(root->left)){
10             return false;
11         }
12 
13         if(root->val<=preVal){
14             return false;
15         }
16         preVal=(long)root->val;//保存前值
17 
18         if(!isValidBST(root->right)){
19             return false;
20         }
21 
22         return true;
23     }
24 };
View Code

 

3.二叉搜索树的最小绝对差

3.1.问题描述

给你一个二叉搜索树的根节点 root ,返回 树中任意两不同节点值之间的最小差值

差值是一个正数,其数值等于两值之差的绝对值。

链接:https://leetcode.cn/problems/minimum-absolute-difference-in-bst/

3.2.要点

由于差值需要是正数,所以必然是大值减小值。

搜索树的中序遍历序列是一个递增序列。

1递归+中序

3.3.代码实例

递归+中序遍历

 1 class Solution {
 2 public:
 3     long minVal=INT_MAX;
 4     long pre=INT_MIN;
 5     int getMinimumDifference(TreeNode* root) {
 6         if(root==NULL){
 7             return INT_MAX;
 8         }
 9 
10         getMinimumDifference(root->left);
11 
12         minVal=min(minVal,root->val-pre);
13         pre=root->val;
14 
15         getMinimumDifference(root->right);
16 
17         return minVal;
18     }
19 };
View Code

 

4.二叉搜索树中的众数

4.1.问题描述

给你一个含重复值的二叉搜索树(BST)的根节点 root ,找出并返回 BST 中的所有 众数(即,出现频率最高的元素)。
如果树中有不止一个众数,可以按 任意顺序 返回。
链接:https://leetcode.cn/problems/find-mode-in-binary-search-tree

4.2.要点

统计+取次数最大值,没有用上搜索树的特性

1递归统计

搜索树的中序遍历为递增序列,统计众数可以使用候选法。第一次遇到的记为1,与前一个相同的记为+1,达到maxCnt时记录下来,超过maxCnt时更新maxCnt并清空前值、记录当前节点值

2候选法

4.3.代码实例

递归统计

 1 class Solution {
 2 public:
 3     map<int,int> numTimes;
 4     vector<int> ret;
 5     void counting(TreeNode* root){
 6         if(root==NULL){
 7             return;
 8         }
 9 
10         counting(root->left);
11         
12         numTimes[root->val]++;
13 
14         counting(root->right);
15     }
16 
17     vector<int> findMode(TreeNode* root) {
18         if(root==NULL){
19             return ret;
20         }
21 
22         counting(root);
23         int cnt=INT_MIN;
24         for(auto& p:numTimes){
25             cnt=max(cnt,p.second);
26         }
27         for(auto& p:numTimes){
28             if(p.second==cnt){
29                 ret.push_back(p.first);
30             }
31         }
32 
33         return ret;
34     }
35 };
View Code

候选法

 1 class Solution {
 2 private:
 3     int count;
 4     int maxCount;
 5     TreeNode* pre;
 6     vector<int> result;
 7     void searchBST(TreeNode* cur) {
 8         if (cur == NULL) return ;
 9 
10         searchBST(cur->left);       //11                                     //
12         if (pre == NULL) { // 第一个节点
13             count = 1;
14         } else if (pre->val == cur->val) { // 与前一个节点数值相同
15             count++;
16         } else { // 与前一个节点数值不同
17             count = 1;
18         }
19         pre = cur; // 更新上一个节点
20 
21         if (count == maxCount) { // 如果和最大值相同,放进result中
22             result.push_back(cur->val);
23         }
24 
25         if (count > maxCount) { // 如果计数大于最大值
26             maxCount = count;
27             result.clear();     // 很关键的一步,不要忘记清空result,之前result里的元素都失效了
28             result.push_back(cur->val);
29         }
30 
31         searchBST(cur->right);      //
32         return ;
33     }
34 
35 public:
36     vector<int> findMode(TreeNode* root) {
37         int count = 0; // 记录元素出现次数
38         int maxCount = 0;
39         TreeNode* pre = NULL; // 记录前一个节点
40         result.clear();
41 
42         searchBST(root);
43         return result;
44     }
45 };
View Code

 

5.普通二叉树的最近公共祖先问题

5.1.问题描述

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
链接:https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree

5.2.要点

经典题型,注意逻辑

递归+后序遍历

递归解析:
(1)终止条件:
        当越过叶节点,则直接返回 null;
        当 root等于 p,q ,则直接返回 root;
(2)递推工作:
        开启递归左子节点,返回值记为 left ;
        开启递归右子节点,返回值记为 right;
(3)返回值: 根据 left 和 right,可展开为四种情况;
        当 left 和 right 同时为空 :说明 root的左 / 右子树中都不包含 p,q ,返回 null;
        当 leftl 和 right 同时不为空 :说明 p,q 分列在 root的 两侧 (分别在 左 / 右子树),因此 root 为最近公共祖先,返回 root;
        当 left为空 ,right不为空 :p,q都不在 root的左子树中,直接返回 right。具体可分为两种情况:
            p,q其中一个在 root的 右子树 中,则此时 right 必指向 p或q其中一个;
            p,q两节点都在 root的 右子树 中,此时的 right是下层递归返回回来的“最近公共祖先节点”;
        当 left不为空 , right为空 :与情况 3. 同理;
解答链接:https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/solution/236-er-cha-shu-de-zui-jin-gong-gong-zu-xian-hou-xu/

解答链接:https://github.com/hitwzy/leetcode-master/blob/master/problems/0236.%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%9C%80%E8%BF%91%E5%85%AC%E5%85%B1%E7%A5%96%E5%85%88.md

5.3.代码实例

递推+后序遍历

 1 class Solution {
 2 public:
 3     TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
 4         //中止条件:越过了叶子节点,或者遇到了p/q
 5         if(root==NULL||root==p||root==q){
 6             return root;
 7         }
 8 
 9         //后序遍历搜索
10         TreeNode* left=lowestCommonAncestor(root->left,p,q);
11         TreeNode* right=lowestCommonAncestor(root->right,p,q);
12 
13         //两侧都没有,情况1
14         if(left==NULL&&right==NULL){
15             return NULL;
16         }
17 
18         //一侧有一侧没有时,情况3、4
19         if(left==NULL) return right;
20         if(right==NULL) return left;
21 
22         //都不为空,则p/q在root的两侧,情况2
23         return root;
24     }
25 };
View Code

 

6.搜索树的最近公共祖先

6.1.问题描述

给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
链接:https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-search-tree

6.2.要点

用普通二叉树找公共祖先的方法也可以求解,但没有利用上搜索树的有序性。

如果两个目标节点都小于当前节点,那么它们都在左子树上,则递归左子树。大于,右子树,递归搜索右子树。

如果一大一小,则说明当前节点是两个目标节点分流的地方,当前节点即最近公共祖先。

1迭代,忽略root判空、p/q不存在、计算溢出等问题;

2递归

6.3.代码实例

迭代

1     public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
2         //如果根节点和p,q的差相乘是正数,说明这两个差值要么都是正数要么都是负数,也就是说
3         //他们肯定都位于根节点的同一侧,就继续往下找
4         while ((root.val - p.val) * (root.val - q.val) > 0)
5             root = p.val < root.val ? root.left : root.right;
6         //如果相乘的结果是负数,说明p和q位于根节点的两侧,如果等于0,说明至少有一个就是根节点
7         return root;
8     }

递归+后序遍历

 1 class Solution {
 2 public:
 3     TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
 4         if(root==NULL||root==p||root==q){
 5             return root;
 6         }
 7 
 8         //后序遍历
 9         //p\q都在root的左侧
10         if(root->val>p->val&&root->val>q->val){
11             return lowestCommonAncestor(root->left,p,q);
12         }
13         //p\q都在root的右侧
14         if(root->val<p->val&&root->val<q->val){
15             return lowestCommonAncestor(root->right,p,q);
16         }
17 
18         //p\q分属两边
19         return root;
20     }
21 };
View Code

 

7.搜索树中的插入操作

7.1.问题描述

给定二叉搜索树(BST)的根节点 root 和要插入树中的值 value ,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据 保证 ,新值和原始二叉搜索树中的任意节点值都不同。
注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回 任意有效的结果 。
链接:https://leetcode.cn/problems/insert-into-a-binary-search-tree

7.2.要点

1模拟法

将新值与根节点比较,小于则插入到左子树、大于则插入到右子树。

如果子树不为空,递归进入子树进行比较。

如果子树为空,新建节点接上即可。

7.3.代码实例

模拟法

 1 class Solution {
 2 public:
 3     TreeNode* insertIntoBST(TreeNode* root, int val) {
 4         if(root==NULL){
 5             root=new TreeNode(val);
 6             return root;
 7         }
 8 
 9         if(root->val>val){
10             root->left=insertIntoBST(root->left,val);
11         }
12         else{
13             root->right=insertIntoBST(root->right,val);
14         }
15 
16         return root;
17     }
18 };
View Code

 

8.搜索树中的删除操作

8.1.问题描述

给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。
一般来说,删除节点可分为两个步骤:
(1)首先找到需要删除的节点;
(2)如果找到了,删除它。
链接:https://leetcode.cn/problems/delete-node-in-a-bst

8.2.要点

需要画图理解,根据二叉搜索树的性质:
(1)如果目标节点大于当前节点值,则去右子树中删除;
(2)如果目标节点小于当前节点值,则去左子树中删除;
(3)如果目标节点就是当前节点,分为以下三种情况:
        1.其无左子:其右子顶替其位置,删除了该节点;
        2.其无右子:其左子顶替其位置,删除了该节点;
        3.其左右子节点都有:其左子树转移到其右子树的最左节点的左子树上,然后右子树顶替其位置,由此删除了该节点。

8.3.代码实例

递归

 1 class Solution {
 2 public:
 3     TreeNode* deleteNode(TreeNode* root, int key) 
 4     {
 5         if (root == nullptr)    return nullptr;
 6         if (key > root->val)    root->right = deleteNode(root->right, key);     // 去右子树删除
 7         else if (key < root->val)    root->left = deleteNode(root->left, key);  // 去左子树删除
 8         else    // 当前节点就是要删除的节点
 9         {
10             if (! root->left)   return root->right; // 情况1,欲删除节点无左子
11             if (! root->right)  return root->left;  // 情况2,欲删除节点无右子
12             TreeNode* node = root->right;           // 情况3,欲删除节点左右子都有 
13             while (node->left)          // 寻找欲删除节点右子树的最左节点
14                 node = node->left;
15             node->left = root->left;    // 将欲删除节点的左子树成为其右子树的最左节点的左子树
16             root = root->right;         // 欲删除节点的右子顶替其位置,节点被删除
17         }
18         return root;    
19     }
20 };
View Code

 

9.修剪一颗搜索树

9.1.问题描述

给你二叉搜索树的根节点 root ,同时给定最小边界low 和最大边界 high。通过修剪二叉搜索树,使得所有节点的值在[low, high]中。修剪树 不应该 改变保留在树中的元素的相对结构 (即,如果没有被移除,原有的父代子代关系都应当保留)。 可以证明,存在 唯一的答案 。
所以结果应当返回修剪好的二叉搜索树的新的根节点。注意,根节点可能会根据给定的边界发生改变。

链接:https://leetcode.cn/problems/trim-a-binary-search-tree

9.2.要点

若 root.val 小于边界值 low,则 root 的左子树必然均小于边界值,我们递归处理 root.right 即可;
若 root.val 大于边界值 high,则 root 的右子树必然均大于边界值,我们递归处理 root.left 即可;
若 root.val 符合要求,则 root 可被保留,递归处理其左右节点并重新赋值即可。

1递归

9.3.代码实例

递归

 1 class Solution {
 2 public:
 3     TreeNode* trimBST(TreeNode* root, int low, int high) {
 4         if(root==NULL){
 5             return root;
 6         }
 7 
 8         if(root->val>high){
 9             root=trimBST(root->left,low,high);
10         }
11         else if(root->val<low){
12             root=trimBST(root->right,low,high);
13         }
14         else{
15             root->left=trimBST(root->left,low,high);
16             root->right=trimBST(root->right,low,high);
17         }        
18         
19         return root;
20     }
21 };
View Code

 

10.构造一颗搜索树

10.1.问题描述

给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 高度平衡 二叉搜索树。
高度平衡 二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。
链接:https://leetcode.cn/problems/convert-sorted-array-to-binary-search-tree

10.2.要点

可以将递增序列看做搜索树的中序遍历,根节点即递增序列的中值。然后分别对左右子树进行构建。

10.3.代码实例

递归

 1 class Solution {
 2 public:
 3     TreeNode* build(vector<int>& nums,int left,int right){
 4         if(left>right){
 5             return NULL;
 6         }
 7         int mid=left+(right-left)/2;
 8         TreeNode* newNode=new TreeNode(nums[mid]);
 9         newNode->left=build(nums,left,mid-1);
10         newNode->right=build(nums,mid+1,right);
11 
12         return newNode;
13     }
14     TreeNode* sortedArrayToBST(vector<int>& nums) {
15         int dataLen=nums.size();
16         
17         return build(nums,0,dataLen-1);
18     }
19 };
View Code

 

11.搜索树转成累加树

11.1.问题描述

 给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。

链接:https://leetcode.cn/problems/convert-bst-to-greater-tree/

11.2.要点

搜索树的中序遍历是递增序列,本问题所求为递增序列从后往前加,因此使用中序遍历的翻转。

11.3.代码实例

中序遍历的倒序

 1 class Solution {
 2 public:
 3     long curSum=0;
 4     TreeNode* convertBST(TreeNode* root) {
 5         if(root==NULL){
 6             return NULL;
 7         }
 8 
 9         convertBST(root->right);
10         curSum+=root->val;
11         root->val=curSum;
12         convertBST(root->left);        
13 
14         return root;
15     }
16 };
View Code

 

xxx.问题

xxx.1.问题描述

111

xxx.2.要点

222

xxx.3.代码实例

333

posted @ 2022-12-18 15:27  啊原来是这样呀  阅读(8)  评论(0编辑  收藏  举报