代码随想录算法训练营第十八天 | 513. 找树左下角的值、112. 路径总和、106. 从中序与后序遍历序列构造二叉树

60 阅读5分钟

513. 找树左下角的值

链接

文章链接

题目链接

第一想法

因为这道题是找最底层的最左边的叶子节点,所以首先想到了用层序法,代码如下:

function findBottomLeftValue(root: TreeNode | null): number {
   let queue:(TreeNode | null)[]=[]
   let node:TreeNode;
   if(root) queue.push(root)
   while(queue.length>0){
       let size=queue.length
       node=queue[0]!
       while(size--){
           let node=queue.shift()!
           if(node.left) queue.push(node.left)
           if(node.right) queue.push(node.right)
       }
   }
   return node!.val
};

看完文章后的想法

文章用了递归法和迭代法(也就是上面的层序法),这道题递归法是比较不容易想的,递归法需要确保两个条件,一个是最底层,另一个是最左。如何确保是最深呢,当然是用一个辅助变量帮忙确定,如何确定最左,只需要在递归过程中保证先递归左侧,再递归右侧就可以了,代码如下:

function findBottomLeftValue(root: TreeNode | null): number {
    let maxdepth:number=Number.MIN_SAFE_INTEGER
    let res:number=0
    const dfs:(root: TreeNode | null,depth:number)=>void=(root: TreeNode | null,depth:number)=>{
        if(!root!.left&&!root!.right){
            if(depth>maxdepth){
                maxdepth=depth
                res=root!.val
            }
                return 
        }
            if(root!.left) dfs(root!.left,depth+1)
            if(root!.right) dfs(root!.right,depth+1)
    }
    dfs(root,0)
    return res
};

思考

这道题不是很难,但是这是一道层序法比递归法好写的一道题,因为递归需要考虑两个条件,一个是最底层,另一个是最左,只要把这两个条件考虑清楚就可以用递归写出解法。

112. 路径总和

链接

文章链接

题目链接

第一想法

看到路径和,首先想到了用递归法,其次需要一个辅助变量存入路径的节点和,到叶子节点后判断路径和是否和目标相同,代码如下:

function hasPathSum(root: TreeNode | null, targetSum: number): boolean {
    if (!root) return false
    let boolean: boolean = false
    const dfs: (root: TreeNode | null, sum: number) => void = (root: TreeNode | null, sum: number) => {
        if (!root!.left && !root!.right) {
            if (sum + root!.val == targetSum) boolean = true
            return
        }
        if (root!.left) dfs(root!.left, sum + root!.val)//隐藏着sum的回溯
        if (root!.right) dfs(root!.right, sum + root!.val)
    }
    dfs(root, 0)
    return boolean
};

看完文章后的想法

代码随想录中用了两种方法:迭代法和递归法。这里详细介绍递归法,文章中的递归和我的递归有所区别,我算的是加法,文章算的解法,不过我用到了辅助变量boolean,文章中没有用到,所以尝试不用辅助变量,代码如下:

function hasPathSum(root: TreeNode | null, targetSum: number): boolean {
    if(!root) return false
  const dfs:(root: TreeNode | null,sum:number)=>boolean=(root: TreeNode | null,sum:number)=>{
      if(root==null) return false
      if(!root.left&&!root.right){
          return sum+root.val==targetSum
      }
      return dfs(root.left,sum+root.val)|| dfs(root.right,sum+root.val)
  }
  return  dfs(root,0)
};

思考

这道题用递归法不难,但是自己的想法没有很优,看完文章后发现可以不用辅助变量boolean,以后遇到题需要考虑一下是否需要返回值,做完这道题后,尝试了一下113. 路径总和 II,这道题的区别是要存储路径上的值,所以不需要返回值,但是要注意数组的回溯,代码如下:

function pathSum(root: TreeNode | null, targetSum: number): number[][] {
 if(root==null) return []
  let res:number[][]=[]
  const dfs:(root: TreeNode,arr:number[],sum:number)=>void=(root: TreeNode,arr:number[],sum:number)=>{
      if(!root.left&&!root.right){
          if(sum==targetSum){
              let array=Array.from(arr)
              res.push(array)
          }
          return
      }
      if(root.left){
          arr.push(root.left.val)
          dfs(root.left,arr,sum+root.left.val)//隐含对sum的回溯 因为传参传的是值
          arr.pop()//回溯
      }
       if(root.right){
          arr.push(root.right.val)
          dfs(root.right,arr,sum+root.right.val)
          arr.pop()//回溯
      }
  }
  dfs(root,[root.val],root.val)
  return res
};

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

链接

文章链接

题目链接

第一想法

给出后序或者前序,再加上中序遍历求完整的二叉树这是一种常见提醒,核心思想是依据后序和中序构建完整的二叉树,推导可以在代码随想录中查看,后序遍历的目标节点的前一个它的右孩子,这里就上代码了(因为要构建二叉树,需要从下向上构建二叉树,所以用递归法且需要返回值):

function buildTree(inorder: number[], postorder: number[]): TreeNode | null {
//left:中序遍历  right:后序遍历
const foo=(leftorder: number[],rightorder:number[])=>{
if(rightorder.length==0) return null
if(rightorder.length==1) return new  TreeNode(rightorder[0])
let rootValue:number=rightorder.pop()!
let node:TreeNode=new TreeNode(rootValue)
let index:number=leftorder.indexOf(rootValue)
let leftInorder:number[]=leftorder.slice(0,index)//左节点的中序
let rightInorder:number[]=leftorder.slice(index+1,leftorder.length)//右节点的中序
let leftPostorder:number[]=rightorder.slice(0,leftInorder.length)//左节点的后序
let rightPostorder:number[]=rightorder.slice(leftInorder.length,leftInorder.length+rightInorder.length)//右节点的后序
node.left=foo(leftInorder,leftPostorder) //左
node.right=foo(rightInorder,rightPostorder)//右
return node //中
}
return foo(inorder,postorder)
};

看完文章后的想法

文章讲的非常详细,其中有几个注意点我再列举一下:

  1. 首先是一定要用后序遍历来解决这个问题,为什么不能用前序能,原因是你只能找到目标节点的右节点,找不到目标节点的左节点

  2. 代码随想录中的一张图可以完美解释如何利用中序遍历和后序遍历构建二叉树: image.png

  3. 终止条件比较好判断,如果后序数组为空,则返回null,如果后序数组为一个值,而直接将这个值构建成二叉树的节点返回就行

  4. 核心思想是后序遍历的最后一个值是中间节点,所以需要通过中间节点来分割中序遍历的数组,分为左右两棵树的中序遍历

  5. 为什么先分割中序呢,因为有个隐藏条件中序数组的大小一定等于后序数组的大小,所以先将中序分割成左右两棵树的左右后,左右两棵树的中序数组的大小就能切割后序数组

思考

这道题很难,虽然我手写过这种题,但是转换为代码就不太会了,需要看着代码随想录中的文章才能完整的写出来,下面是尝试105. 从前序与中序遍历序列构造二叉树

function buildTree(preorder: number[], inorder: number[]): TreeNode | null {
  const foo=(preorder: number[], inorder: number[])=>{
  if(preorder.length==0) return null
  if(preorder.length==1) return new TreeNode(preorder[0])
  let value:number=preorder.shift()!
  let node=new TreeNode(value)
  let index:number=inorder.indexOf(value)
  let leftInorder:number[]=inorder.slice(0,index)//左子树的中序
  let rightInorder:number[]=inorder.slice(index+1,inorder.length)//右子树的中序
  
  let leftPreorder:number[]=preorder.slice(0,leftInorder.length)//左子树的前序
  let rightPreorder:number[]=preorder.slice(leftInorder.length,leftInorder.length+rightInorder.length)//右子树的前序
  node.left=foo(leftPreorder,leftInorder)
  node.right=foo(rightPreorder,rightInorder)
  return node
  }
  return foo(preorder,inorder)
  };

今日总结

今日总共完成了五道题,其中主要练习的递归法,要理解递归法中的回溯原理,这会帮助我很好的理解代码思路,今天的最后两道题:一道用中序和后序构建二叉树和一道用前序和中序构建二叉树难度比较大,耗时比较久。今日总共耗时3.5小时