一开始还以为这个问题很简单,平时练习的时候也没有多在意,直到。。。百度二面后一个算法加面以及字节跳动视频面都出现了这个问题,才发现并不是想象中的那么简单。
找了别人的几篇博客看了看,发现别人好像都有过总结。。就有点尴尬,想来想去还是记录下来吧,敲一遍印象更深。。。【我只是代码的搬运工】
———————————————– 我是正文分割线————————————————–
1. 二叉查找树定义?性质?
任意一个节点的值一定大于该节点左子树中的任意一个节点的值,同时满足该节点的值小于其右子树中的任意一个节点的值。
2. 二叉查找树数据结构定义(个人版本)
class TreeNode{
int data;
TreeNode left;
TreeNode right;
//构造函数
....
}
3. 错误方法
很不好意思的是,这两次面试每次遇到这个问题总是脱口而出:
“递归判断每个节点和其左右节点值的大小关系”
非常遗憾,这种方法是错误的 是错误的 是错误的 是错误的 是错误的,只能判断是否为局部二叉查找树,反例就是这个:
先不管是否正确吧,这个方法的思路如下:
public boolean isTree(TreeNode root){
if(root == null) return true;
if(root.left != null && root.left.val > root.val)
return false;
if(root.right != null && root.right.val < root.val)
return false;
if(!isTree(root.left) || !isTree(root.right))
return false;
return true;
}
4. 使用中序遍历判断二叉查找树(保存到数组中)
这个方法的思路也很简单,根据二叉查找树的性质我们可以知道:如果我们用中序遍历遍历二叉查找树的话,遍历到的每个节点是依次增大的。根据这个思路,我们可以遍历一遍树,将遍历到的值保存到数组中,然后再判断这个数组是否为递增数组就可以了。下面是算法片段:
List<Integer> list=new ArrayLIst<>();
public void isTree(TreeNode root){
if(root == NULL) return;
isTree(root.left);
list.add(root.val);
isTree(root.right);
}
public static void main(String[] args){
//假设给定了树的根节点root
isTree(root);
for(int i = 1; i < list.size(); i++)
if(list.get(i-1) >= list.get(i]))
return false;
return true;
}
5. 使用中序遍历判断二叉查找树(保存中序遍历上一个节点值)
每当我说可以用数组保存遍历顺序,然后判断数组是否为递增数组后,面试官总会问“还有没有更简单的方法,使用O(1)的空间复杂度?”
其实面试官的意思就是:只让你保存遍历节点的上一个节点值,不需要另外开辟空间。因为中序遍历是递增的,所以只需要比较上一个节点值和该节点值的大小即可。递归算法思路就是如下:
int last = Integer.MIN_VALUE;
public boolean isTree(TreeNode root){
if(root == null)
return true;
if(!isTree(root.left))
return false;
if(root.val <= last)
return false;
last=root.val;
if(!isTree(root.right))
return false;
return true;
}
有的时候面试官还要问能不能用迭代写出来,我就一块写一下吧。迭代算法思路就是使用栈进行存储,先将左子树压入栈,然后依次取出判断。代码如下:
public boolean isBST(TreeNode root) {
Stack<TreeNode> stack = new Stack<>();
TreeNode p = root, pre =n ull;
while(p != null || !stack.isEmpty()){
while(p != null){
stack.push(p);
p = p.left;
}
TreeNode t = stack.pop();
if(pre != null && t.val <= pre.val)
return false;
pre = t;
p = t.right;
}
return true;
}
6. 使用Morris Traversal方法
这个方法也是从别人那里看到的一种遍历二叉树的方法,不用栈存储而且为空间复杂度为O(1)。思路就是利用叶子节点中的左右空指针指向中序遍历下的前驱节点或后继节点,从而得到判断条件。在一开始last我用的是int last=Ineger.MIN_VALUE
结果在leetcode上跑的时候有个测试用例是 -2147483648,正好是MIN_VALUE,然后就判断错误,于是用了其包装类型。代码如下:
public boolean isValidBST(TreeNode root) {
if(root == null)
return true;
Integer last = null;
TreeNode cur=root, pre=null;
while(cur != null){
if(cur.left != null){
pre = cur.left;
while(pre.right != null && pre.right != cur)
pre = pre.right;
if(pre.right == null){
pre.right = cur;
cur = cur.left;
}else{
pre.right = null;
if(last != null && cur.val <= last)
return false;
last = cur.val;
cur = cur.right;
}
}else{
if(last != null && cur.val <= last)
return false;
last = cur.val;
cur = cur.right;
}
}
return true;
}
6. 参考文章
当然,还有好多方法没有写出来,比如说这个: https://blog.csdn.net/FlushHip/article/details/70941221 用每个子树的最大最小值和根节点值比较得到结果。
感谢一下大神提供思路以及代码。。。
递归非递归方法
https://blog.csdn.net/qq_30490125/article/details/53135274
Morris Traversal方法
https://www.cnblogs.com/AnnieKim/archive/2013/06/15/MorrisTraversal.html
Leetcode上题目
https://leetcode-cn.com/problems/validate-binary-search-tree