Leetcode 98:验证二叉搜索树
题目描述
给定一个二叉树,判断其是否是一个有效的二叉搜索树。
假设一个二叉搜索树具有如下特征:
- 节点的左子树只包含小于当前节点的数。
- 节点的右子树只包含大于当前节点的数。
- 所有左子树和右子树自身必须也是二叉搜索树。
示例1:
输入:
2
/ \
1 3
输出: true
示例2:
输入:
5
/ \
1 4
/ \
3 6
输出: false
解释: 输入为: [5,1,4,null,null,3,6]。
根节点的值为 5 ,但是其右子节点值为 4 。
我的解法:非递归的中序遍历
利用中序遍历思想,代码见下
bool isValidBST(TreeNode* root) {
TreeNode* p = root;
stack<TreeNode*> s;
int temp = 233333; // 随便设置的,无意义
bool isTempUpdate = false;
while (p != NULL || !s.empty())
{
while (p != NULL)
{
s.push(p);
p = p->left;
}
if (!s.empty())
{
p = s.top();
if (isTempUpdate && temp >= p->val) return false;
temp = p->val;
isTempUpdate = true;
s.pop();
p = p->right;
}
}
return true;
}
一开始代码中并没有isTempUpdate这个bool变量,而是直接把INT_MIN赋值给temp,在输入是[-2147483648]的时候报错了,因为INT_MIN值就是-2147483648,在执行循环时temp的初始值和树的val相等,返回false。
一开始的代码见下:
bool isValidBST(TreeNode* root) {
TreeNode* p = root;
stack<TreeNode*> s;
int temp = INT_MIN;
while (p != NULL || !s.empty())
{
while (p != NULL)
{
s.push(p);
p = p->left;
}
if (!s.empty())
{
p = s.top();
if (temp >= p->val) return false;
temp = p->val;
s.pop();
p = p->right;
}
}
return true;
}
为了解决这个问题,我加上了一个bool类型变量以记录temp的值是否更新过,只有当其值更新过且小于当前树的值时才返回true,因此我可以给temp赋任意值。
bool isValidBST(TreeNode* root) {
TreeNode* p = root;
stack<TreeNode*> s;
int temp = 233333; // 随便设置的,无意义
bool isTempUpdate = false;
while (p != NULL || !s.empty())
{
while (p != NULL)
{
s.push(p);
p = p->left;
}
if (!s.empty())
{
p = s.top();
if (isTempUpdate && temp >= p->val) return false;
temp = p->val;
isTempUpdate = true;
s.pop();
p = p->right;
}
}
return true;
}
其它解法1:非递归的中序遍历,用LONG_MIN规避树的val等于INT_MIN的情况
代码见下:
bool isValidBST(TreeNode* root) {
TreeNode* p = root;
stack<TreeNode*> s;
long temp = LONG_MIN;
while (p != NULL || !s.empty())
{
while (p != NULL)
{
s.push(p);
p = p->left;
}
if (!s.empty())
{
p = s.top();
if (temp >= p->val) return false;
temp = p->val;
s.pop();
p = p->right;
}
}
return true;
}
发现执行时间并没有缩短,反而内存占用多了一点点。。。
其他解法2:递归的中序遍历思想
首先,运用递归的中序遍历思想,递归的中序遍历代码如下:
void inorder(TreeNode *root, vector<int> &path)
{
if (root != NULL)
{
inorder(root->left, path);
path.push_back(root->val);
inorder(root->right, path);
}
}
然后遍历比较vector里的元素即可,完整代码如下:
bool isValidBST(TreeNode* root) {
vector<int> path;
inorder(root, path);
if (path.empty()) return true;
for (int i = 0; i < path.size() - 1; i++)
{
if (path[i] >= path[i + 1]) return false;
}
return true;
}
void inorder(TreeNode* root, vector<int> &path) {
if (!root) return;
inorder(root->left, path);
path.push_back(root->val);
inorder(root->right, path);
}
其它解法3:即时比较的递归中序遍历
这个解法跟上个解法类似,不同之处在于这个解法没有用vector,而是在递归函数中进行比较的。
大概原理是基于递归的中序遍历,然后在函数中的两次递归调用都加一次返回。inorder(root->left, pre)是仅为false时才返回,如果是true就继续执行后续判断;inorder(root->right, pre)是在函数的最后,无论true还是false均要返回。除了这两处递归调用要返回,还有函数本身要返回的地方就是比较pre->val 和此时输入参数root的val大小,返回 true或者 false。
bool isValidBST(TreeNode* root) {
TreeNode* pre = NULL;
return inorder(root, pre);
}
bool inorder(TreeNode* root, TreeNode* &pre)
{
if (!root) return true;
bool res = inorder(root->left, pre);
if (!res) return false;
if (pre)
{
if (pre->val >= root->val) return false;
}
pre = root;
return inorder(root->right, pre);
}
其它解法4:节点继承法
这题实际上简化了难度,因为有的时候题目中的二叉搜索树会定义为左<=根<右,而这道题设定为一般情况左<根<右,那么就可以用中序遍历来做。因为如果不去掉左=根这个条件的话,那么下边两个数用中序遍历无法区分:
1 1
/ \
1 1
因此,可以采用我自己命名的节点继承法(只是命名,不是我的方法233333),能够区分出来是左节点还是右节点的情况,代码如下:
bool isValidBST(TreeNode* root) {
return isValidBST(root, LONG_MIN, LONG_MAX);
}
bool isValidBST(TreeNode* root, long mn, long mx) {
if (!root) return true;
if (root->val <= mn || root->val >= mx) return false;
return isValidBST(root->left, mn, root->val) && isValidBST(root->right, root->val, mx);
}
不太好理解,可以这样想:我为什么称之为节点继承法,就是因为每个节点的值都要跟两个值比较,大值和小值,每一次的递归都要保证当前节点的val大于大值,小于小值。而大值和小值都是继承自上一个节点所用到的大值或小值,即递归函数的第二个和第三个参数。
举一个例子,比如有这样一个树:
5
/ \
3 8
/ \
2 9
首先,看值为5的节点,它上面没有父节点,因此mn和mx分别设置为LONG_MIN和LONG_MAX即可,然后进入下一层递归,先是左节点root(3)要小于父节点的值,因此第二个参数mn设置为父节点的val,即root(5)->val。而对于第三个参数mx,要保证值为3的这个节点小于它,直接继承下来上一次递归所用的第三个参数,意思是3虽然要小于5,但是也要大于root(5)的左父节点(左父节点不存在,就用LONG_MIN)。
如果还不理解,可以继续往下看,进入下一层递归,直接看root(9),递归函数所用的第一个参数是root(3)->right,第三个参数是root(3)->val,这没什么问题。再看第二个参数,直接继承的上一次递归,也就是判断3节点是用的第二个参数root(5)->val,意思是root(3)->val要小于root(5)->val,其后所有子节点都要小于root(5)->val。
总结下来,就是父节点所要大于或小于的东西,子节点也要满足,可以理解为继承关系。还拿root(9)来说,它首先继承了root(3)节点,要大于root(3)大于的LONG_MIN,也要小于root(3)小于的root(5)->val,继承完再看它自己的新条件,因为root(3)是root(9)的左父节点,因此root(9)->val要大于root(3)->val。