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。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值