终于开启了树的篇章,这是我数据库领域经常用到的算法,这是第一篇,力扣链接。
给你一个二叉树的根节点
root
,判断其是否是一个有效的二叉搜索树。有效 二叉搜索树定义如下:
- 节点的左子树只包含 小于 当前节点的数。
- 节点的右子树只包含 大于 当前节点的数。
- 所有左子树和右子树自身必须也是二叉搜索树。
示例 1:
输入:root = [2,1,3] 输出:true
这道题很有意思,我一开始的提交方法是:
func isValidBST(root *TreeNode) bool {
if root == nil || (root.Left == nil && root.Right == nil) {
return true
}
if root.Right == nil {
if root.Left.Val < root.Val {
return isValidBST(root.Left)
}
} else if root.Left == nil {
if root.Right.Val < root.Val {
return isValidBST(root.Right)
}
} else if root.Left.Val < root.Val && root.Right.Val > root.Val {
return isValidBST(root.Left) && isValidBST(root.Right)
}
return false
}
我认为,只有右子叶大于根节点,左子叶小于根节点就行,其实不是。这里的要求二叉树是左树都得比根节点小,右树都得比根节点大。
所以这里的递归可能不是和当前节点比左右叶子结点,而是根最大最小的边界比根节点。
func isValidBST(root *TreeNode) bool {
return helper(root, math.MinInt, math.MaxInt)
}
func helper(root *TreeNode, lower int, upper int) bool {
if root == nil {
return true
}
if root.Val <= lower || root.Val >= upper {
return false
}
return helper(root.Left, lower, root.Val) && helper(root.Right, root.Val, upper)
}
当然,递归可以做的迭代也能搞。我们把树封装进数组,进行中序遍历,大概逻辑就是将左节点放入栈中,一个一个取出和父节点与右节点比较大小,右节点一定要比根节点大。
func isValidBST(root *TreeNode) bool {
stack := make([]*TreeNode, 0)
rootVal := math.MinInt
for len(stack) > 0 || root != nil {
for root != nil {
stack = append(stack, root)
root = root.Left
}
root = stack[len(stack)-1]
stack = stack[:len(stack)-1]
if root.Val <= rootVal {
return false
}
rootVal = root.Val
root = root.Right
}
return true
}
抛开这道题,我们讲一下遍历二叉树的三种方式吧:
在计算机科学中,对一棵树(特别是二叉树)的遍历是指按照某种顺序访问树中的每个节点,确保每个节点都被访问一次的过程。对于二叉树,最常见的遍历方式有三种:中序遍历、前序遍历和后序遍历。这些遍历方式可以递归或迭代地实现。
中序遍历 (Inorder Traversal)
在中序遍历中,我们按照以下顺序遍历树中的节点:
- 遍历左子树
- 访问根节点
- 遍历右子树
对于二叉搜索树(BST),中序遍历会按照升序访问所有节点,因为二叉搜索树的特点是左子节点的值小于根节点的值,根节点的值小于右子节点的值。
前序遍历 (Preorder Traversal)
在前序遍历中,节点的访问顺序如下:
- 访问根节点
- 遍历左子树
- 遍历右子树
前序遍历通常用于创建树的副本。当你访问节点之后立即复制节点,你可以通过前序遍历复制所有节点并创建一棵相同的树。
后序遍历 (Postorder Traversal)
后序遍历的顺序是:
- 遍历左子树
- 遍历右子树
- 访问根节点
后序遍历常用于删除或释放树中的节点。因为你在删除节点之前先访问其子节点,这样可以安全地删除每个节点。
例子
假设有一棵二叉树如下:
A / \ B C / \ \ D E F
对这棵树进行不同的遍历会得到以下结果:
- 中序遍历:
D, B, E, A, C, F
。首先遍历左子树(D, B, E),然后是根节点(A),最后是右子树(C, F)。- 前序遍历:
A, B, D, E, C, F
。首先访问根节点(A),然后是左子树(B, D, E),最后是右子树(C, F)。- 后序遍历:
D, E, B, F, C, A
。首先是左子树(D, E, B),然后是右子树(F, C),最后是根节点(A)。遍历可以通过递归或使用栈的迭代方法来实现。递归是最直观的方法,但在某些情况下(如树非常深时),迭代方法可能更优,因为它可以避免递归导致的栈溢出问题。
这里就不过多的展开,后续一定会遇到相应的题目。