我们一起聊聊十五周算法—二叉搜索树(BST)

开发 前端
BST有一个重要的性质:BST的中序遍历结果是有序的(升序),也就是在中序位置可以将每个节点的值升序打印出来。

今天是十五周算法训练营的第五周,主要讲二叉搜索树专题,包含:验证二叉搜索树、不同的二叉搜索树、二叉树的最近公共祖先、二叉搜索树的最近公共祖先。(欢迎加入十五周算法训练营,与小伙伴一起卷算法)

BST的特性:

对于BST的每一个节点node,左子树节点的值都比node的值要小,右子树节点的值都比node的值大;

对于BST的每一个节点node,它的左侧子树和右侧子树都是BST。

BST有一个重要的性质:BST的中序遍历结果是有序的(升序),也就是在中序位置可以将每个节点的值升序打印出来

void traverse(TreeNode root) {
     if (root == null) return;
     traverse(root.left);
     // 中序遍历代码位置
     print(root.val);
     traverse(root.right);
}

验证二叉搜索树

给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。

有效 二叉搜索树定义如下:

节点的左子树只包含 小于 当前节点的数。 节点的右子树只包含 大于 当前节点的数。 所有左子树和右子树自身必须也是二叉搜索树。

示例 1:

输入:root = [2,1,3] 输出:true

根据二叉搜索树特性,通过中序遍历获取。

// 解题思路:
// 1. 是否可以通过遍历一遍二叉树得到答案?
// 通过中序遍历可以得到,因为BST的中序遍历是一个升序结果
function isValidBST1(root) {
    let inorder = -Infinity;
    let result = true;
    let traverse = root => {
        if (root === null) {
            return;
        }

        // 前序位置
        traverse(root.left);
        // 中序位置
        // 中序位置获取当前值,并比较其和前一个值的大小,如果小于等于前一个值,则证明不是BST树,则将结果变为false
        if (root.val <= inorder) {
            result = false;
        } else {
            inorder = root.val;
        }
        traverse(root.right);
        // 后续位置
    };

    traverse(root);

    return result;
}

// 将中序遍历变为循环的方式
function isValidBST2(root) {
    if (root === null) {
        return true;
    }
    let head = root;
    const stack = [];
    let inorder = -Infinity;

    // 中序遍历的循环结构是需要创建一个栈,然后先将左节点全压入栈中
    while (stack.length > 0 || head !== null) {
        // 当head不为空时,压入栈中
        if (head !== null) {
            stack.push(head);
            head = head.left;
        } else {
            // 弹出栈顶元素
            head = stack.pop();
            if (inorder >= head.val) {
                return false;
            }
            inorder = head.val;
            head = head.right;
        }
    }

    return true;
}

// 2. 是否可以定义一个递归函数,通过子问题(子树)的答案推导出原问题的答案?
// 答案是可以的,因为搜索二叉树的性质是:
// (1)对于BST的每一个节点node,左子树节点值都比node的值要小,右子树节点的值逗比node的值大;
// (2)对于BST的每一个节点node,它的左侧子树和右侧子树都是BST
// 则我们解决该问题可定义一个递归函数来进行解决

// 注意:该问题需要借助外部变量,因为根节点的值必须大于左子树所有值、小于右子树所有值,所以需要引入最大最小值

function isValidBST3(root) {
    // 构造一个辅助函数,判断根节点是否在(min,max)范围内
    const helper = (root, min, max) => {
        if (root === null) {
            return true;
        }

        if (root.val <= min) {
            return false;
        }

        if (root.val >= max) {
            return false;
        }

        return helper(root.left, min, root.val) && helper(root.right, root.val, max);
    };

    // 初始状态min为-Infinity,max为Infinity
    return helper(root, -Infinity, Infinity);
}

不同的二叉搜索树

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

示例 1:」

图片

输入:n = 3

输出:5

利用的也是左子树值 < 根节点 < 右子树值

// 对于BST的每一个节点node,左子树节点的值都比node的值要小,右子树节点的值都比node的值大

// 在该问题中,1~n都有可能成为根节点,然后左边的是左子树上的点,右边的是右子树上的点,然后对应根节点的搜索树数量就是左子树的组合数 * 右子树的组合数;

// 该问题明显就转换为一个递归问题

function numTree(n) {
    // 为了解决子问题的重复问题,需要引入备忘录
    const memo = [];
    for (let i = 0; i < n + 1; i++) {
        memo.push([]);
        for (let j = 0; j < n + 1; j++) {
            memo[i].push(0);
        }
    }
    const count = (low, high) => {
        // 递归终止条件
        if (low > high) {
            return 1;
        }

        // 判断备忘录中是否存在该值,存在的话直接使用
        if (memo[low][high] > 0) {
            return memo[low][high];
        }
        let result = 0;

        for (let i = low; i <= high; i++) {
            const left = count(low, i - 1);
            const right = count(i + 1, high);

            result += left * right;
        }

        // 将结果存储到备忘录中
        memo[low][high] = result;

        return result;
    };

    return count(1, n);
}

console.log(numTree(3));

二叉树的最近公共祖先

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

示例 1:

图片

输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1 输出:3 解释:节点 5 和节点 1 的最近公共祖先是节点 3 。

// 通过递归解决(后续遍历)
function lowestCommonAncestor(root, p, q) {
    // 确定递归函数
    const traverse = (node, p, q) => {
        // 确定递归终止条件
        if (node === null || node === p || node === q) {
            return node;
        }

        const left = traverse(node.left, p, q);
        const right = traverse(node.right, p, q);

        // 后续位置
        // 找到一个节点,其发现p、q节点分别出现在其左右子树上
        if (left !== null && right !== null) {
            return node;
        }

        // p或q本身就是最近公共祖先
        if (left === null) {
            return right;
        }

        return left;
    };

    return traverse(root, p, q);
}

二叉搜索树的最近公共祖先

给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

例如,给定如下二叉搜索树:  root = [6,2,8,0,4,7,9,null,null,3,5]

示例 1:

图片

输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8 输出: 6 解释: 节点 2 和节点 8 的最近公共祖先是 6。

// 因为是二叉搜索树,其是有顺序的,所以找最近公共祖先节点的问题就转换成了该节点在[p, q]中间即可
function lowestCommonAncestor(root, p, q) {
    const helper = (node, p, q) => {
        if (node === null) {
            return node;
        }

        if (node.val > p.val && node.val > q.val) {
            const left = helper(node.left, p, q);

            if (left !== null) {
                return left;
            }
        }

        if (node.val < p.val && node.val < q.val) {
            const right = helper(node.right, p, q);
            if (right !== null) {
                return right;
            }
        }

        return node;
    };

    return helper(root, p, q);
}

责任编辑:武晓燕 来源: 前端点线面
相关推荐

2023-05-08 07:32:03

BFSDFS路径

2023-02-01 07:27:46

序列化二叉树根节点

2023-06-19 07:31:34

普通动态规划字符串

2021-09-02 11:31:28

二叉搜索树迭代法公共祖先

2021-08-26 11:31:11

二叉树数据结构算法

2023-10-10 08:00:07

2022-08-30 13:48:16

LinuxMySQL内存

2021-08-27 07:06:10

IOJava抽象

2024-02-20 21:34:16

循环GolangGo

2023-08-04 08:20:56

DockerfileDocker工具

2023-06-30 08:18:51

敏捷开发模式

2023-09-10 21:42:31

2022-05-24 08:21:16

数据安全API

2023-08-10 08:28:46

网络编程通信

2022-10-28 07:27:17

Netty异步Future

2022-04-06 08:23:57

指针函数代码

2022-01-04 12:08:46

设计接口

2022-06-26 09:40:55

Django框架服务

2022-11-12 12:33:38

CSS预处理器Sass

2023-04-26 07:30:00

promptUI非结构化
点赞
收藏

51CTO技术栈公众号