二叉树part5 | ● 513.找树左下角的值 ● 112. 路径总和 113.路径总和ii ● 106.从中序与后序遍历序列构造二叉树 105.从前序与中序遍历序列构造二叉树


513.找树左下角的值

思路

层次遍历找到每层最左边的节点,最后一层的值就是所要求的左下角的值。
时间复杂度On
空间复杂度On

代码

func findBottomLeftValue(root *TreeNode) int {
    q:=[]*TreeNode{}
    q=append(q,root)
    res:=0
    for len(q)>0{
        isfirst:=true
        lenth:=len(q)
        for i:=0;i<lenth;i++{
            cur:=q[0]
            if isfirst{
                res=cur.Val
                isfirst=false
            }
            q=q[1:]
            if cur.Left!=nil{
                q=append(q,cur.Left)
            }
            if cur.Right!=nil{
                q=append(q,cur.Right)
            }
        }
    }
    return res
}

困难

使用两层循环区分每一层的节点。


112. 路径总和

思路

递归:
递归参数返回值:参数节点和目标值,返回值能否有到根节点路径为目标值的路径。
终止条件:节点为空,节点为叶子节点
单层递归逻辑:判断左右子节点能否找到路径总和为目标值减去当前节点值的路径。
时间复杂度On
空间复杂度Oh

代码

func hasPathSum(root *TreeNode, targetSum int) bool {
    if root==nil{
        return false
    }
    if root.Left==nil&&root.Right==nil&&root.Val==targetSum{
        return true
    }
    return hasPathSum(root.Left,targetSum-root.Val)||hasPathSum(root.Right,targetSum-root.Val)
}

113.路径总和ii

113.路径总和ii

思路

递归回溯
前序遍历递归,记录路径,要记得回溯。
时间复杂度On
空间复杂度On

代码

func pathSum(root *TreeNode, targetSum int) [][]int {
    res:=[][]int{}
    path:=[]int{}
    var dfs func(root *TreeNode, targetSum int)
    dfs=func(root *TreeNode, targetSum int){
        if root==nil{
            return
        }
        path=append(path,root.Val)
        if root.Left==nil&&root.Right==nil&&root.Val==targetSum{
            temp:=[]int{}
            temp=append(temp,path...)
            res=append(res,temp)
        }
        dfs(root.Left,targetSum-root.Val)
        dfs(root.Right,targetSum-root.Val)
        path=path[:len(path)-1]
    }
    dfs(root,targetSum)
    return res
}

困难

考虑回溯,同时由于切片是引用类型,要记得copy。


106.从中序与后序遍历序列构造二叉树

106.从中序与后序遍历序列构造二叉树

思路

后序遍历是左右中,中序遍历是左中右
所以可以根据后序遍历的末尾在中序遍历数组中找到对应位置,从而在后序遍历数组中区分左右子树。而对子树的处理又和原来一样,所以可以采用递归的方式。
时间复杂度On
空间复杂度On

官方题解

代码

func buildTree(inorder []int, postorder []int) *TreeNode {
    imap:=make(map[int]int)
    res:=&TreeNode{}
    for k,v:=range inorder{
        imap[v]=k
    }
    var build func(node *TreeNode,i,l,r int)
    build = func(node *TreeNode, i,l,r int){
        node.Val=postorder[r]
        in:=imap[node.Val]
        if in-i>l{
            node.Left=&TreeNode{}
            build(node.Left,i,l,in-1-i)
        }
        if in-i<r{
            node.Right=&TreeNode{}
            i++
            build(node.Right,i,in-i+1,r-1)
        }
    }
    build(res,0,0,len(postorder)-1)
    return res
}

困难

后序遍历对应子树在中序遍历数组的位置是会不断发生偏移的,每判断一次右子树,中序遍历就会往右偏移一位,所以要统计右子树的判断次数,同时将对应位置减去偏移的位数。

if in-i<r{
            node.Right=&TreeNode{}
            i++
            build(node.Right,i,in-i+1,r-1)
        }

要注意i++应该放在末尾,防止影响其他递归的参数。


105.从前序与中序遍历序列构造二叉树

105.从前序与中序遍历序列构造二叉树

思路

前序遍历是中左右,中序遍历是左中右
所以可以根据前序遍历的开头在中序遍历数组中找到对应位置,从而在前序遍历数组中区分左右子树。而对子树的处理又和原来一样,所以可以采用递归的方式。
时间复杂度On
空间复杂度On

代码

func buildTree(preorder []int, inorder []int) *TreeNode {
    imap:=make(map[int]int)
    res:=&TreeNode{}
    for k,v:=range inorder{
        imap[v]=k
    }
    var build func(node *TreeNode,i,l,r int)
    build = func(node *TreeNode,i,l,r int){
        node.Val=preorder[l]
        in:=imap[node.Val]
        if in+i<r{
            node.Right=&TreeNode{}
            build(node.Right,i,in+i+1,r)
        }
        if in+i>l{
            node.Left=&TreeNode{}
            i++
            build(node.Left,i,l+1,in+i-1)
        }        
    }
    build(res,0,0,len(preorder)-1)
    return res
}

困难

前序遍历对应子树在中序遍历数组的位置是会不断发生偏移的,每判断一次左子树,中序遍历就会往左偏移一位,所以要统计左子树的判断次数,同时将对应位置加上偏移的位数。

if in+i>l{
            node.Left=&TreeNode{}
            i++
            build(node.Left,i,l+1,in+i-1)
        }  

要注意i++应该放在末尾,防止影响其他递归的参数。


今日收获

对数的递归回溯有了更深刻的认识。
了解了根据前中后序数组递归构造二叉树的算法。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值