【LeetCode】12.二叉树系列——搜索树
总目录:
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 };
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 };
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 };
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 };
候选法
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 };
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/
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 };
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 };
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 };
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 };
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 };
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 };
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 };
xxx.问题
xxx.1.问题描述
111
xxx.2.要点
222
xxx.3.代码实例
333