LeetCode 98. Validate Binary Search Tree
题目很短,大概讲的是要求是判断输入是否是一个BST。其中BST的定义如下:
- 左子树的所有结点都比根小
- 右子树的所有结点都比根大
- 所有左子树和右子树都是BST
题目的第一个坑是,题目并没有把BST的全部定义写全了,漏了下面这个:
- 没有相同值的结点
为了不给大家提示,题目里的例题给的是前序遍历的排序。其实很容易想到只需要做一次中序遍历,然后检查遍历的队列是否是单调递增的就可以了。
中序遍历方法
static list<int> serializeBST;
class Solution {
public:
bool isValidBST(TreeNode* root) {
if(root == NULL)
return true;
serializeBST.clear();
printBST(root);
list<int>::const_iterator i = serializeBST.begin();
i++;
list<int>::const_iterator j = serializeBST.begin();
for(;i != serializeBST.end(); i++,j++)
{
if(*j >= *i)
return false;
}
return true;
}
void printBST(TreeNode* root)
{
if(root != NULL)
{
printBST(root->left);
serializeBST.push_back(root->val);
printBST(root->right);
}
return;
}
};
二叉树的遍历的递归中,每个结点都需要遍历一次,时间复杂度是O(n),每次list的push_back操作时间复杂度是O(1),最后遍历一次队列时间复杂度是O(n)。所以总的时间复杂度是O(n)+O(n)。而最差情况是严重不平衡的二叉搜索树,递归调用深度是O(n),空间复杂度是O(n)。而list的空间复杂度也是O*(n),所以总的空间复杂度是O(n)。
- 答主用了static关键字,用来在static区上创建一个数组,事实上这样会减低代码的runtime,推荐写进class做为一个private的变量,这样结果一样,也不会那么慢
然后当然没完,这样写的话,无论什么情况,都必须遍历整个二叉树后才能判断是否是BST,这样太笨了,能不能一旦找到了错误的地方就立刻报false呢?这样最坏情况和上次方法一致,但平均时间复杂度变成了O(log*n)。而通过方法一,很明显我们也发现在比较的步骤里,永远只需要前一位和后一位比较大小,所以只需要存前一位的大小就可以了,这样优化的空间复杂度是*O(1)。
优化的中序遍历
class Solution {
private:
int g_leftmax = -1;
bool first = true;
public:
bool isValidBST(TreeNode* root) {
if(root == NULL)
return true;
if(!isValidBST(root->left))
return false;
if(!first && root->val <= g_leftmax)
return false;
first = false;
g_leftmax = root->val;
if(!isValidBST(root->right))
return false;
return true;
}
};
代码更加简洁了,不过这里有几个点要注意的。
- LeetCode最近更新过一次test case,增加了int的边界条件,最小的结点值可以去到INT_MIN,所以如果不用first判断是否第一次的话,很有可能会出现第一个点的值就等于g_leftmax,从而报false。当然,网上有一些是将g_leftmax实现为一个初始值为NULL的TreeNode指针,这样就可以通过判断指针是否为空来判断是否为第一次
这样就快多了,而且空间复杂度也优化了不少。
既然有递归,我们自然可以用栈来做非递归的方法,这样理论上可以降低递归深度很大的时候的函数调用的时间开销。(但结果是栈实现的方法runtime更加大……这就神奇了。)
用栈实现的非递归方法
class Solution {
public:
bool isValidBST(TreeNode* root) {
stack<TreeNode*> s;
TreeNode *p = root, *pleftmax= NULL;
while (p || !s.empty()) {
while (p) {
s.push(p);
p = p->left;
}
TreeNode *t = s.top(); s.pop();
if (pleftmax&& t->val <= pleftmax->val)
return false;
pleftmax= t;
p = t->right;
}
return true;
}
};
拓展
这道题拓展开来,可以有:
- 左子树小于等于根结点,而右子树大于根结点,这样中序历遍就无法解决问题了(10,10到底是左子树是10–合法,还是右子树是10–非法)