题目:
给你一个二叉树的根节点
root
,判断其是否是一个有效的二叉搜索树。有效 二叉搜索树定义如下:
- 节点的左子树只包含 小于 当前节点的数。
- 节点的右子树只包含 大于 当前节点的数。
- 所有左子树和右子树自身必须也是二叉搜索树。
目录
二叉搜索树性质:
- 如果该二叉树的左子树不为空,则左子树上所有节点的值均小于它的根节点的值;
- 若它的右子树不空,则右子树上所有节点的值均大于它的根节点的值;
- 它的左右子树也为二叉搜索树。
根据二叉搜索树的性质,如是中序遍历二叉搜索树的话,我们将得到一个升序序列。我们可以根据这个特性来尝试解决这道题目:
解法一:中序遍历(自下而上判断)
我们将所给的二叉搜索树中序遍历,判断得到的序列是不是严格的升序序列。
代码如下:
class Solution {
ArrayList<Integer> list = new ArrayList<Integer>();
public boolean isValidBST(TreeNode root) {
//中序遍历
inordered(root);
//判断list是不是严格升序
for(int i = 0; i < list.size() - 1; i++){
if(list.get(i) >= list.get(i + 1)){
return false;
}
}
return true;
}
//中序遍历该二叉搜索树
public void inordered(TreeNode root){
if(root == null){
return;
}
inordered(root.left);
list.add(root.val);
inordered(root.right);
}
这种解法有点傻白甜,查看了一些大神解法后,用同样的思路进行代码优化:可以在中序遍历该树的同时,用当前与中序遍历的前一个节点进行比较,若是当前节点值小于前一个节点,则不满足升序,直接返回false。
尝试代码:
class Solution {
long pre = Long.MIN_VALUE;
public boolean isValidBST(TreeNode root) {
if(root == null){
return true;
}
//递归左子树
isValidBST(root.left);
//判断当前节点值是否小于前一个节点
if(root.val <= pre){
return false;
}
//将当前节点值赋给pre,与下一个节点进行比较
pre = root.val;
//
return isValidBST(root.right);
}
}
提交以后发现有bug,经过调试,发现问题所在:
当root.left > pre,返回 false 后,我们想要的结果是直接结束递归,将false返回即可。 但在对root.left 递归结束后回溯的过程中,上述代码返回false后,只是结束了当前节点的右节点的递归,待回溯下一次root.left的时候,并不能确保右节点不进行递归,这样就会对结果false进行覆盖,相当于上述程序对是否是二叉搜索树的判断结果完全由右树决定。
解决bug:
我们已经知道之所以false被覆盖,是因为发现问题节点后没有对后续右节点的递归进行截断处理。我们可以对每次左树递归的回溯结果保留,进行判断,如果为false,则不用执行下面代码(包括对右节点的递归)。
最终代码:
class Solution {
long pre = Long.MIN_VALUE;
public boolean isValidBST(TreeNode root) {
if(root == null){
return true;
}
//处理:对每次递归结果进行判断,如果为false,则直接返回,进行下一次回溯,不用执行下面代码
if(!isValidBST(root.left)){
return false;
}
if(root.val <= pre){
return false;
}
pre = root.val;
return isValidBST(root.right);
}
}
解法二 :DFS(自上而下判断)
利用二叉搜索树的性质1和性质2,我们可以构造递归函数来判断当前节点值是否大于左子树,小于右子树,isValidBST(root , l ,r),判断root.val是否在开区间(l , r)之间。
代码实现:
class Solution {
public boolean isValidBST(TreeNode root) {
return isValidBST(root,Long.MIN_VALUE,Long.MAX_VALUE);
}
public boolean isValidBST(TreeNode root,long lower,long upper){
if(root == null){
return true;
}
//判断root.val是否在(lower,upper)之间
if(root.val <= lower || root.val >= upper){
return false;
}
//左右子树同时满足性质1,2
return isValidBST(root.left,lower,root.val) && isValidBST(root.right,root.val,upper);
}
}
等价写法:
class Solution {
public boolean isValidBST(TreeNode root) {
return isValidBST(root,Long.MIN_VALUE,Long.MAX_VALUE);
}
public boolean isValidBST(TreeNode root,long lower,long upper){
if(root == null){
return true;
}
//判断root.val是否在(lower,upper)之间
if(root.val <= lower || root.val >= upper){
return false;
}
//对左子树结果进行判断
if(!isValidBST(root.left,lower,root.val)){
return false;
}
//若左子树返回true,结果就由右子树确定
return isValidBST(root.right,root.val,upper);
}
}
上述用long而不用int,是为了扩大节点值的范围。若使用int,如果测试用例的节点值超出int的范围也会报错,为了避免,我们使用long。