JS:二叉搜索树(2)——后序遍历、递归改迭代

手把手带你刷通二叉搜索树(第三期)——如何计算所有合法 BST

96. 不同的二叉搜索树 (中等)fail

给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。

什么方式能够正确地穷举合法 BST 的数量呢?
在这里插入图片描述
所以如果固定3作为根节点,左子树节点就是{1,2}的组合,右子树就是{4,5}的组合。
左子树的组合数和右子树的组合数乘积就是3作为根节点时的 BST 个数。
在这里插入图片描述
函数的含义为:计算闭区间 [1, n] 组成的 BST 个数。当拆解这个区间时,肯定存在重叠子问题。

因为二叉搜索树起的二分作用,所以每次拆解后的两边区间都是连续的

let dp = null;
var numTrees = function(n) {
    dp = new Array(n+1).fill(0).map(val => new Array(n+1).fill(0))
    return count(1,n)
};
function count(st, en) {
    if(st >= en) return 1;
    // 查备忘录
    if(dp[st][en]) return dp[st][en]
    // 当前根节点的众数
    let res = 0;
    for(let i=st; i<=en; i++){
        let left = count(st, i-1)
        let right = count(i+1, en)
        res += (left * right)
    }
    // 将结果存入备忘录
    dp[st][en] = res;
    return res;
}

95. 不同的二叉搜索树 II (中等)

给你一个整数 n ,请你生成并返回所有由 n 个节点组成且节点值从 1 到 n 互不相同的不同 二叉搜索树 。可以按 任意顺序 返回答案。

在这里插入图片描述
思路与上一题一样:
1、穷举root节点的所有可能。
2、递归构造出左右子树的所有合法 BST。
3、给root节点穷举所有左右子树的组合。

var generateTrees = function(n) {
    return build(1, n)
};
function build(st, en) {
    // 返回的是列表
    if(st>en) return [null]
    if(st==en) return [new TreeNode(st)]
    let res = []
    for(let i=st; i<=en; i++){
        let lefts = build(st, i-1)
        let rights = build(i+1, en)
        for(left of lefts) {
            for(right of rights) {
                let root = new TreeNode(i)
                root.left = left
                root.right = right
                res.push(root)
            }
        }
    }
    return res;
}

在这里插入图片描述

1373. 二叉搜索子树的最大键值和 (困难)

对于有的题目,不同的遍历顺序时间复杂度不同。

如果当前节点要做的事情需要通过左右子树的计算结果推导出来,就要用到后序遍历。

给你一棵以 root 为根的 二叉树 ,请你返回 任意 二叉搜索子树的最大键值和。

注意:
最大的二叉树不一定是二叉搜索树
在这里插入图片描述
虽然节点4的值最大,但不能把它单独看

在这里插入图片描述
在这里插入图片描述
最核心的思路是明确当前节点需要做的事情是什么。——最终目的是计算BST 的最大和。
在这里插入图片描述
如果前序遍历,就要使用三个递归辅助函数,输入是当前根节点,然后获取上面三点的信息。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

以 root 为根的二叉树不是 BST,那他的父亲(往上的节点)也不是BST
根节点要大于左子树的最大值,小于右子树的最小值
而根节点的最小值在左子树找,最大值在右子树找
这两个地方要分清

let maxsum = 0
var maxSumBST = function(root) {
    maxsum = 0
    traverse(root)
    return maxsum
};
// 返回值 []是否为BST, 最小值, 最大值, 节点值之和]
function traverse(root) {
    if(root == null) return [true, null, null, 0]
    let left = traverse(root.left)
    let right = traverse(root.right)

    // 后序遍历操作
    let res = new Array(4)
    // 判断以 root 为根的二叉树是不是 BST
    if(left[0] && right[0] && (left[2]==null || left[2]<root.val) 
        && (right[1]==null || right[1]>root.val)) {
        res[0] = true
        // 计算以 root 为根的这棵 BST 的最小值
        res[1] = left[1]==null? root.val : Math.min(left[1], root.val)
        // 计算以 root 为根的这棵 BST 的最大值
        res[2] = right[2]==null? root.val : Math.max(right[2], root.val)
        // 计算以 root 为根的这棵 BST 所有节点之和
        res[3] = left[3] + right[3] + root.val
        // 更新全局变量
        maxsum = Math.max(maxsum, res[3])
    } else {
        res[0] = false
    }
    return res
}   

501. 二叉搜索树中的众数 (简单) fail

在这里插入图片描述

清空数组元素
与有序数组中查找众数一样,需要一些变量来获取信息

var findMode = function(root) {
    let maxcount = 1, curcount = 1, pre = null;
    let res = []
    function traverse(root) {
        if(root == null) return
        traverse(root.left)
        // 中序遍历操作
        if(pre == null) { // 处理第一个节点
            res.push(root.val)
        } else {
            if(root.val == pre.val) {
                curcount++
                if(curcount == maxcount){
                    res.push(root.val)
                } else if(curcount > maxcount){
                    res = []
                    maxcount = curcount
                    res.push(root.val)
                }
            } else {
                curcount = 1
                if(curcount == maxcount) res.push(root.val)
            }
        }
        // 记录上一个节点
        pre = root
        traverse(root.right)
    }
    traverse(root)
    return res
};

530.783. 二叉搜索树的最小绝对差 (简单)

给你一个二叉搜索树的根节点 root ,返回 树中任意两不同节点值之间的最小差值 。
差值是一个正数,其数值等于两值之差的绝对值。

上一题的弟弟

var getMinimumDifference = function(root) {
    let res = Number.MAX_SAFE_INTEGER, pre = null
    function traverse(root) {
        if(root == null) return
        traverse(root.left)
        if(pre) {
            res = Math.min(res, (root.val-pre.val))
        }
        pre = root
        traverse(root.right)
    }
    traverse(root)
    return res
};

669. 修剪二叉搜索树 (中等)

在这里插入图片描述

独立分析:

  • 当前root不在区间内:(根节点不能用)
    1.如果小于区间,看右子树;2.如果大于区间,看左子树
    - 当前root在区间内:(先等左右子树出结果——后序遍历)
    root左子树的最大值小于区间, 剪掉
    root右子树的最小值大于区间,剪掉

函数的定义:返回合法的子树——先序遍历

var trimBST = function(root, low, high) {
    if(root == null) return null
    if(root.val < low) {
        return trimBST(root.right, low, high)
    } else if(root.val > high) {
        return trimBST(root.left, low, high)
    }
    root.left = trimBST(root.left, low, high)
    root.right = trimBST(root.right, low, high)
    return root
};

最后学一下:递归改迭代,因为数据结构也经常问怎么将中后序遍历使用迭代的方式。
文章认为是因为:面试越来越卷,他的观点:二叉树的题目还是用递归的方式来做,因为递归是最符合二叉树结构特点的。

二叉树八股文:递归改迭代

这篇没有放相关的题目,重点是理解怎么迭代,最直接题目就是二叉树的遍历。下面两幅图是迭代的核心。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
不管是什么遍历,节点入栈的顺序都是一样的
虽然这框架通用性好,但有点难理解和为啥他满足各种情况
145. 二叉树的后序遍历
94. 二叉树的中序遍历

**后序遍历:**
function pushleft(root) {
    while(root) {
        stack.push(root)
        root = root.left
    }
}
var postorderTraversal = function(root) {
    stack = []
    let visited = new TreeNode(-1), res = []
    pushleft(root)
    while(stack.length) {
        let cur = stack[stack.length-1]
        // p 的左子树被遍历完了,且右子树没有被遍历过——现在到根节点
        if((cur.left==null || cur.left==visited) && cur.right!=visited) {
            pushleft(cur.right)
        }
        // p 的右子树被遍历完了
        if(cur.right==null || cur.right==visited){
            // 后序遍历:等右子树根节点遍历完才到自己
            res.push(cur.val)
            visited = stack.pop()
        }
    }
    return res;
};

中序遍历

var inorderTraversal = function(root) {
    stack = []
    let visited = new TreeNode(-1), res = []
    pushleft(root)
    while(stack.length) {
        let cur = stack[stack.length-1]
        // p 的左子树被遍历完了,且右子树没有被遍历过——现在到根节点
        if((cur.left==null || cur.left==visited) && cur.right!=visited) {
            // 中序遍历:自己先读再去找右子树
            res.push(cur.val)
            
            // 去遍历 p 的右子树
            pushleft(cur.right)
        }
        // p 的右子树被遍历完了
        if(cur.right==null || cur.right==visited){
            // 后序遍历:等右子树根节点遍历完才到自己
            // res.push(cur.val)

            // 以 p 为根的子树被遍历完了,出栈
            visited = stack.pop()
        }
    }
    return res;
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值