LeetCode #98 判断搜索二叉树
本文记录了完成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(struct TreeNode* root) {
if (root == NULL || (root->left == NULL && root->right == NULL))
return true;
if (root->left != NULL)
if (root->left->val >= root->val)
return false;
if (root->right != NULL)
if (root->right->val <= root->val)
return false;
return (isValidBST(root->right) && isValidBST(root->left));
}
不出意外的解答错误。
因为我只分析了子树中的情况,没有考虑各元素与根节点的大小关系。
第二次 比较成功的测试:
因为我想用前序遍历试一下能不能写好这道题,于是参考了网上某位的解答思路:
- 先遍历左子树,查找是否有元素大于根节点;
- 再遍历右子树,同上;
- 最后按照第一次的方式,检查各个子树中的关系。
这里我原本是只遍历了所有元素与最初的那个根节点的大小关系,没有遍历与其他根节点的关系。
之后我在CheckLeft
和CheckRight
里面加上了递归遍历所有元素与根节点的大小关系,这样之后最后一条检查各个子树中的关系就显得有些多余了,因为我把那段注释掉之后代码仍然测试成功。
第二次的代码
bool CheckLeft(struct TreeNode* root, struct TreeNode* tmp);
bool CheckRight(struct TreeNode* root, struct TreeNode* tmp);
bool CheckTree(struct TreeNode* root);
bool isValidBST(struct TreeNode* root)
{
if (root == NULL || (root->left == NULL && root->right == NULL))
return true;
bool tmpLeft = true, tmpRight = true;
if (root->left != NULL)
{
if (!CheckLeft(root, root->left))
return false;
}
if (root->right != NULL)
{
if (!CheckRight(root, root->right))
return false;
}
return true;
}
bool CheckLeft(struct TreeNode* root, struct TreeNode* tmp)
{
bool tmpLeft = true, tmpRight = true;
if (tmp->val >= root->val)
return false;
if (tmp->left != NULL)
tmpLeft = (CheckLeft(root, tmp->left) && CheckLeft(tmp, tmp->left));
else
tmpLeft = true;
if (tmp->right != NULL)
tmpRight = (CheckLeft(root, tmp->right) && CheckRight(tmp, tmp->right));
else
tmpRight = true;
return (tmpLeft && tmpRight);
}
bool CheckRight(struct TreeNode* root, struct TreeNode* tmp)
{
bool tmpLeft = true, tmpRight = true;
if (tmp->val <= root->val)
return false;
if (tmp->left != NULL)
tmpLeft = (CheckRight(root, tmp->left) && CheckLeft(tmp, tmp->left));
else
tmpLeft = true;
if (tmp->right != NULL)
tmpRight = (CheckRight(root, tmp->right) && CheckRight(tmp, tmp->right));
else
tmpRight = true;
return (tmpLeft && tmpRight);
}
这段代码总体上是成功的,但是执行时间过长。
于是我放弃了用前序遍历写这道题的尝试,转向中序遍历。
第三次 中序遍历的成功
了解到 “用中序遍历二叉搜索树输出的数列是递增数列” 后,我就开始尝试用中序遍历写出这道题。
这是第三次的代码
int counter;
void Order(struct TreeNode* root, int* arr);
bool isValidBST(struct TreeNode* root)
{
if (root == NULL || (root->left == NULL && root->right == NULL))
return true;
int i, arr[10240] = {0};
counter = 0;
Order(root, arr);
for (i = 1; i < counter; i++)
{
if (arr[i] <= arr[i-1])
return false;
}
return true;
}
void Order(struct TreeNode* root, int* arr)
{
if (root->left != NULL)
Order(root->left, arr);
arr[counter] = root->val;
counter++;
if (root->right != NULL)
Order(root->right, arr);
return;
}
由于思路比较成熟,所以这次是一次过。
由于需要一个变量实时记录录入的值得数量,但受到C语言的限制不能用int&
这样传递实参的方法,所以我设置了一个全局变量counter
。
先用Order
把二叉搜索树中序遍历输出到arr
,再循环查看是否有逆序的情况,有则false
。
由于OJ过题的时间时长时短,而我的代码居然跑了20ms,于是我再跑了一次,跑到8ms。
第四次 改进的测试
我是在第三次尝试过了之后来写的这篇文章,写完已经是凌晨三点多,于是去洗澡。洗澡的时候想着20ms的成绩不太对劲,思考怎么提升比较好,于是想到可以把遍历完输出再比较改成遍历时比较,就有了接下来的代码:
这是第四次的代码
int counter;
int Order(struct TreeNode* root, int* arr);
bool isValidBST(struct TreeNode* root)
{
if (root == NULL || (root->left == NULL && root->right == NULL))
return true;
int i, arr[10240] = {0};
counter = 0;
if (Order(root, arr) == 1)
return false;
else
return true;
}
int Order(struct TreeNode* root, int* arr)
{
if (root->left != NULL)
if (Order(root->left, arr) == 1)
return 1;
arr[counter] = root->val;
if (counter > 0)
if (arr[counter] <= arr[counter - 1])
return 1;
counter++;
if (root->right != NULL)
if (Order(root->right, arr) == 1)
return 1;
return 0;
}
流程是这样的:
1. 当输出第二个数时,开始与前一个进行大小比较,大则继续,小则返回1
;
2. 递归中的函数发现有1
返回时,停止并退出递归,否则继续递归并返回0;
3. 函数isValidBST
中发现调用的函数Order
返回1
时,返回false
,否则返回true
。
这次测试通过的时间是4ms,是本题最快的时间之一。