98#验证二叉搜索树
题目描述
给定一个二叉树,判断其是否是一个有效的二叉搜索树。
假设一个二叉搜索树具有如下特征:
- 节点的左子树只包含小于当前节点的数。
- 节点的右子树只包含大于当前节点的数。
- 所有左子树和右子树自身必须也是二叉搜索树。
示例 1:
输入:
2
/ \
1 3
输出: true
示例 2:
输入:
5
/ \
1 4
/ \
3 6
输出: false
解释: 输入为: [5,1,4,null,null,3,6]。
根节点的值为 5 ,但是其右子节点值为 4 。
分析
这一题乍一看,直觉告诉我就是要用递归的方法,一个初步的思路就是判断当前节点是不是大于左子结点且小于右子结点,然后往下递归,判断左子树和右子树分别是不是二叉搜索树,返回左子树是&&右子树是
。
根据这个思路写了第一个错误版本的答案:
错误版本
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public boolean isValidBST (TreeNode root) {
if (root == null) {
return true;
}
if (root.left == null && root.right == null) {
return true;
} else if (root.left == null) {
return root.val < root.right.val;
} else if (root.right == null) {
return root.val > root.left.val;
}
if (root.val > root.left.val && root.val < root.right.val) {
boolean left_isValid = isValidBST(root.left);
boolean right_isValid = isValidBST(root.right);
return left_isValid && right_isValid;
}else
return false;
}
}
这个版本的错误之处就在于,它只判断当前一棵“小树”是不是满足根节点大于左子结点且小于右子结点,所谓“小树”就是只有根左右三个节点(左或右可能为空),没有扩展到其他更深的或更高的层次。这么说可能有点绕,我们举个例子看一下:
现在有一颗二叉树:
10
/ \
5 15
/ \
6 20
任取一个“小树”,都可以满足根节点大于左子结点且小于右子结点,但是它不是一棵二叉搜索树,因为最下层的6在根节点的右子树中,且6 < 10
。也就是说根节点的右子树中出现了比根节点小的节点,这样就不符合二叉搜索树的定义了。出现这种情况就是因为只考虑了局部范围内是不是满足二叉搜索树的特点。
改进版本
在此基础上,我们修改代码:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public boolean isValidBST (TreeNode root) {
if (root == null) {
return true;
}
if (root.left == null && root.right == null) {
return true;
}
if (root.left != null && root.val <= searchMax(root.left).val) {
// 根节点是否小于左子树中最大节点
return false;
}
if (root.right != null && root.val >= searchMin(root.right).val) {
// 根节点是否大于右子树中最小节点
return false;
}
return isValidBST(root.left) && isValidBST(root.right);
}
private TreeNode searchMin (TreeNode root) {
while (root.left != null) {
root = root.left;
}
return root;
}
private TreeNode searchMax (TreeNode root) {
while (root.right != null) {
root = root.right;
}
return root;
}
}
我们增加了两个辅助方法,用来寻找子树中最大的节点和最小的节点,实现的方法也比较简单,最大节点就是不断往右子树走,走到底,类似的,最小节点就是不断往左子结点走,走到底。这样寻找的依据是二叉搜索树的特点:
- 节点的左子树只包含小于当前节点的数。
- 节点的右子树只包含大于当前节点的数。
- 所有左子树和右子树自身必须也是二叉搜索树。
这里有点疑问的就是第三点了,万一哪一个子树不是二叉搜索树呢?那这样找出来的最大/小节点还是正确的吗?这个时候递归的优点就体现出来了,程序会不断缩小判断范围,直到范围缩小到一棵“小树”内,如果有一棵“小树”不满足定义,整棵树都会不满足,所以就算有一颗子树不是二叉搜索树,它迟早会被揪出来。
在主要的判断方法中,我们现在就不是只看一棵“小树”范围内的判断结果了,是根节点与整个子树的比较。最后再返回左子树是&&右子树是
就可以了。