LeetCode 654. 最大二叉树

发布时间 2023-05-29 15:28:53作者: 小星code

题目链接:LeetCode 654. 最大二叉树

题意:

给定一个不重复的整数数组 nums 。 最大二叉树 可以用下面的算法从 nums 递归地构建:

  • 创建一个根节点,其值为 nums 中的最大值。
  • 递归地在最大值 左边 的 子数组前缀上 构建左子树。
  • 递归地在最大值 右边 的 子数组后缀上 构建右子树。

返回 nums 构建的 最大二叉树 。

解题思路:

递归法

直接按题目描述进行模拟即可。
用递归函数 constructMaximumBinaryTree(nums []int) 表示对于 对于数组nums构建满足题目要求的最大二叉树,
在单层的递归逻辑中,找出当前数组中的最大值,然后最大值左边的数构建左子树,最大值右边的数构建右子树。

递归代码如下:

//需要考虑的问题:1. 如何找到最大值? 2. 找到最大值之后要标记最大值的位置
func constructMaximumBinaryTree(nums []int) *TreeNode {
    var root  TreeNode
    if len(nums) == 0{ //递归退出条件
        return &root
    }
    //递归中的处理逻辑
    max := nums[0]
    index :=0
    for k,v:=range nums{// 遍历数组,找到此时nums数组中的最大值以及下标
        if v > max{
            max = v
            index = k
        }
    }
    root.Val = max
    if index > 0 { //最大值左边的数成为左子树
        root.Left = constructMaximumBinaryTree(nums[:index])
    }
    if index < len(nums)-1 {  //最大值右边的数成为右子树
        root.Right = constructMaximumBinaryTree(nums[index+1:])
    }
    return &root
}

笛卡尔树

其实本题的本质上是通过nums数组构建一个笛卡尔树,因此这里介绍一下笛卡尔树的相关知识。

定义

笛卡尔树是一种二叉树,每一个结点由一个键值二元组 (k,w) 构成。
要求 k 满足二叉搜索树的性质(中序遍历是原序列),而 w 满足堆的性质(左右子树的值大于(/小于)根节点的值)

如果笛卡尔树的 k,w 键值确定,k,w 互不相同,那么这个笛卡尔树的结构是唯一的。

上面这棵笛卡尔树相当于把数组元素值当作键值 w ,把数组下标当作键值 k .
显然可以发现:这棵树的键值 k 满足二叉搜索树的性质,而键值 w 满足小根堆的性质。

其实这是一种特殊的笛卡尔树,键值 k 正好对应数组下标。更一般的情况则是任意二元组构建的笛卡尔树。

栈构建

将元素按键值 k 排序(也就是下标),先后插入当前的笛卡尔树中,那么我们每次插入的元素必然在这个树的右链(从根节点一直往右子树走形成的链) 的末端。

执行这样一个过程:

从下往上比较右链节点与当前加入节点 x 的键值 w:

  • 如果找到右链上的节点 u 满足 w[u] < w[x] ,那么把 x 接到 u 的右儿子上,而以 u 为根的右子树变成了 x 的左子树,然后停止加入过程。

  • 如果没有找到右链上的节点,那么 x 就是新根,把原来的树当成 x 的左儿子。

每次添加值都需要进行一次这样的比较。

构建过程示意图如下图所示:

显然,每个数最多进出右链一次,这个过程可以用栈进行维护,栈中维护当前笛卡尔树右链上的节点,不在右链就弹出,时间复杂度为 O(n)

笛卡尔树代码:


func constructMaximumBinaryTree(nums []int) *TreeNode {
    // 维护一个栈,将右子树存到栈中,
    var st []*TreeNode
    for _,v:=range nums{  //依次从前向后遍历数组
        node := TreeNode{v,nil,nil}   //声明当前的节点

        //如果栈不空,并且栈顶元素的值小于当前元素的值,则进行下面循环,直到栈顶元素的值 不小于 当前元素的值
        for  (len(st) > 0 && st[len(st)-1].Val < node.Val){ 
            node.Left = st[len(st)-1]   //则将栈顶元素变成当前节点的左子树
            st = st[:len(st)-1]  //栈顶元素出栈
        }
        if (len(st) > 0){
            st[len(st)-1].Right = &node   //当栈顶元素不在小于当前元素时,则将当前节点变成栈顶元素的右子树
        }
        st = append(st,&node)   //当前元素入栈
    }

    //循环结束后,如果栈里面的元素 > 1,则将剩余的都弹出,只保留栈底,因为栈底是整个树的根
     for len(st) >1 {
         st = st[:len(st)-1]
     }
     return st[0]
}