问题
今天像往常一样,我随机写了一道算法题目。原题如下
给定一个二叉树,判断其是否是一个有效的二叉搜索树。
假设一个二叉搜索树具有如下特征:
节点的左子树只包含小于当前节点的数。 节点的右子树只包含大于当前节点的数。 所有左子树和右子树自身必须也是二叉搜索树。 示例 1:
输入:
2
/ \
1 3
输出: true
示例 2:
输入:
5
/ \
1 4
/ \
3 6
输出: false
解释: 输入为: [5,1,4,null,null,3,6]。
根节点的值为 5 ,但是其右子节点值为 4 。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/validate-binary-search-tree
这道题我能想到的最简单的方法就是递归(中序遍历)进行判断。问题在哪里尼,首先贴上我的第一版本的java代码。
class Solution {
long last = Long.MIN_VALUE;
public boolean isValidBST(TreeNode root) {
if (root == null) {
return true;
}
if (!isValidBST(root.left)) {
return false;
}
if (root.val < last) {
return false;
}
this.last = root.val;
return isValidBST(root.right);
}
}
一眼看上去,似乎判断都对,返回也是对的。在测试用例跑的时候,发现【1,1】这种输入的时候返回了true.哦,原来是边界条件搞错了,我大意了哈(哈哈哈哈),没有仔细考虑。
if (root.val <= last) {
return false;
}
在用golang再次写这个题目的时候,又发现自己犯了一个低级错误,照样先放原来的代码
func isValidBST(root *TreeNode) bool {
return validBST(root, math.MinInt64)
}
func validBST(root *TreeNode, prev int) bool {
if root != nil {
validBST(root.Left, prev)
if root.Val <= prev {
return false
}
prev = root.Val
validBST(root.Right, prev)
}
return true
}
大家有发现代存在的问题么,这里可以思考一下。
如果没发现的话,那我来讲讲,献丑了。首先递归的时候,每次的递归栈都会保存一份单独的prev,所以prev在后面的调用更新之后,之后的迭代当中未必更新了。所以可想而知,这个代码结果错误了。
那么正确的代码怎么书写尼,代码放在下面:
func isValidBST(root *TreeNode) bool {
prev := math.MinInt64
return validate(root, &prev)
}
func validate(root *TreeNode, prev *int) bool {
if root == nil {
return true
}
if validate(root.Left, prev) == false {
return false;
}
if root.Val <= *prev {
return false;
}
*prev = root.Val
return validate(root.Right, prev)
总结
好了,总结的时刻到了,我们在书写递归代码的时候必须有三个注意。第一,返回条件的书写(很可能造成无限递归)。第二: 临界条件判断, 例如我在java版本的书写当中的少了 = 的判断。 第三,就是对于变量传递要注意,尤其是golang语言刷题必须注意,如必须传递指针。
顺便总结一下递归算法的模版吧~,来自于极客大学的谭朝大佬的总结
public void recur(int level, int param) {
// terminator
if (level > MAX_LEVEL) {
// process result
return;
}
// process current logic
process(level, param);
// drill down
recur( level: level + 1, newParam);
// restore current status
}
第一步: 找出递归终止条件 (terminator)
第二步: 处理当前逻辑 (process current logic )
第三步: 下探到下一层(drill down )
第四步: 清理当前层(restore current status)
很多时候问题的代码只需要前三个步骤足以。