700. 二叉搜索树中的搜索
题目链接
给定二叉搜索树(BST)的根节点 root
和一个整数值 val
。
你需要在 BST 中找到节点值等于 val
的节点。 返回以该节点为根的子树。 如果节点不存在,则返回 null
。
题目分析
什么是二叉搜索树?
- 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 它的左、右子树也分别为二叉搜索树。
递归法
- 递归传入参数以及返回值:
TreeNode* searchBST(TreeNode* root, int val)
- 递归终止条件:有两种情况,1)当我们遍历到叶子节点之后的空节点,说明树中没有这个节点;2)当我们遍历到val匹配的节点,即找到该节点;当遇到以上两种情况时,我们就该返回结果了。
if (root == NULL || root->val == val) return root;
- 单层递归逻辑:如果目标val小于当前节点的val,则说明它在左子树中;如果目标val大于当前节点的val,则说明它在右子树中。
整体cpp代码如下:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
TreeNode* searchBST(TreeNode* root, int val) {
// 递归法
// 递归终止条件:如果当前节点为空或者当前节点就是我们想找的val
if(root == NULL|| root->val == val) return root;
// 单层递归逻辑
TreeNode* result = NULL;
if(val < root->val) result = searchBST(root->left, val);
if(val > root->val) result = searchBST(root->right, val);
return result;
}
};
迭代法
对于一般的二叉树,递归中存在着回溯的过程,即如果我们走一个方向走到头了,要返回到上层走另一个方向。
但是对于二叉搜索树,它的节点是有序排好的,所以不需要回溯,通过和根节点判断就能确定搜索的方向。
整体cpp代码如下:
class Solution {
public:
TreeNode* searchBST(TreeNode* root, int val) {
// 迭代法
while(root!=NULL){
if(val < root->val) root = root->left; // 当前节点数值大于目标val,目标节点只能存在于左子树
else if(val >root->val) root = root->right; // 当前节点数值小于目标val,目标节点只能存在于右子树
else return root; // 当前节点数值等于目标val,找到目标节点,直接返回
}
return root;
}
};
98. 验证二叉搜索树
题目链接
给你一个二叉树的根节点 root
,判断其是否是一个有效的二叉搜索树。
有效 二叉搜索树定义如下:
- 节点的左子树只包含 小于 当前节点的数。
- 节点的右子树只包含 大于 当前节点的数。
- 所有左子树和右子树自身必须也是二叉搜索树。
题目分析
演化为判断数组是否有序
基于二叉搜索树的本质,它的中序遍历是有序的,即从小到大排列,所以直观方法是得到该二叉树的中序遍历数组,然后问题转换为判断数组是否有序。
所以我们根据上述分析可以写出以下代码:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
void traversal(TreeNode* cur, vector<int>& vec){
if(cur==NULL) return;
// 左
traversal(cur->left, vec);
// 中
vec.push_back(cur->val);
// 右
traversal(cur->right, vec);
}
bool isValidBST(TreeNode* root) { // 二叉搜索树的中序遍历结果是有序的
if(root==NULL) return true;
vector<int> vec;
traversal(root, vec);
for(int i = 0; i < vec.size(); i++){
if(i>0 && vec[i] <= vec[i-1])
return false;
}
return true;
}
};
递归的同时判断是否有序
上面是将递归过程转变为数组,最后再判断,那么如何同时在递归过程中进行判断呢?
有两点容易混淆:
- 对于当前递归节点
TreeNode* cur
来说,我们不是单纯比较它的左孩子和右孩子的值,而是左子树和右子树,即是说cur
要比它左子树的任意一个节点大,比右子树的任意一个节点小。 - 这题规定 − 2 31 < = N o d e . v a l < = 2 31 − 1 -2^{31} <= Node.val <= 2^{31} - 1 −231<=Node.val<=231−1,也就是说节点的值可能为int(4个byte)的最小值,我们不能使用最小的int来比较,因此可以初始化比较元素为long long(8个byte)类型。
以上两点明确后,我们可以开始设计递归函数:
- 确定递归参数和返回值
首先定义一个long long类型的全局变量,用来比较遍历的节点是否有序,因为Node.val的值可能是int最小值,我们要设置一个比Node.val都要小的值,所以就初始化为long long最小值。
因为我们要寻找某一条边,所以递归函数要返回bool类型。
long long maxVal = LONG_MIN;
bool isValidBST(TreeNode* cur)
- 确定递归终止条件
if (root == NULL) return true;
- 单层递归逻辑
左中右的处理顺序。
对于当前节点cur
来说,我们要与maxVal
来进行比较,判断当前遍历到的节点是不是有序的,如果不是,就要返回false
,如果是,则要更新maxVal
。
bool left = isValidBST(cur->left); // 左
// 中序遍历,验证遍历的元素是不是从小到大
if (maxVal < cur->val) maxVal = cur->val; // 中
else return false;
bool right = isValidBST(cur->right); // 右
return left && right;
整体cpp代码如下:
class Solution {
public:
long long maxValue = LONG_MIN; // 定义全局变量进行递归时的判断
bool isValidBST(TreeNode* root) {
// 递归终止条件
if(root==NULL) return true;
// 单层递归逻辑
bool left = isValidBST(root->left); // 左
if(maxValue < root->val) // 中
maxValue = root->val; // 更新maxValue的值
else return false;
bool right = isValidBST(root->right); // 右
return left && right; // 总结左右两条边的值并返回
}
};