力扣每日一题(1302:层数最深叶子节点的和)

130 阅读2分钟

题目

给你一棵二叉树的根节点 root ,请你返回 层数最深的叶子节点的和 。

  • 示例1

输入: root = [1,2,3,4,5,null,6,7,null,null,null,null,8]
输出: 15
  • 示例2
输入: root = [6,7,8,2,7,1,3,9,null,1,4,null,null,null,5]
输出: 19

虽然这是一道难度为中等的题目,不过相比于昨天,我感觉更好做一些,因为题目很好理解🐶。

中等的类型的题目大多会有一个套路,同一个套路的题目解题思路基本上大同小异,就和以前解数学题目一样,用一个基本的解题思路,然后根据题目的不同,做出一点改变。

二叉树类型的题目首先得想到他的遍历方法:前序遍历、中序遍历、后续遍历还有广度优先和深度优先遍历

虽然深度优先和前序遍历得到的数据顺序是一样的,但是代码思路略微有所不同。

思路一:广度优先遍历

这道题目是要我们求出最深叶子结点的和,首先就可以想到广度优先遍历的方法。广东优先得到的遍历顺序是1-2-3-4-5-6-7-8,并且可以把每一层的数据分开来,先用JavaScript写一下广度优先的大体思路

// 广度优先(二叉树)
function bfs(node) {
  let q = [], // 暂寸每一层的数据
  step = 0 //记录层数
  q.push(node)
  while (q.length) {
    const size = q.length
    for (let i = 0; i < size; i++) {
      const cur = q.shift()
      console.log(cur)
      if (cur.left != null) {
        q.push(cur.left)
      }
      if (cur.right != null) {
        q.push(cur.right)
      }
    }
    step++
  }
}
  • 大致思路是把每一层的数据都存到q里,每次循环取出数组q的第一个数据记为cur,如果cur存在左子树或者右子树,那么把他们存入到数组q里去,一直循环直到q数据没有数据为止。
  • 这样不仅可以把数据从上到下,从左到右读一遍,并且可以知道这些数据在那一层,根据题目要求,我们把最后一层的数据相加得到的就是答案了。
  • 使用TypeScript根基题目改写代码:
/**
 * Definition for a binary tree node.
 * class TreeNode {
 *     val: number
 *     left: TreeNode | null
 *     right: TreeNode | null
 *     constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
 *         this.val = (val===undefined ? 0 : val)
 *         this.left = (left===undefined ? null : left)
 *         this.right = (right===undefined ? null : right)
 *     }
 * }
 */

function deepestLeavesSum(root: TreeNode | null): number {
    let sum: number  = 0
    let q: TreeNode[] = []
    let temp: number[] = []
    q.push(root)
    while (q.length) {
        const size: number = q.length
        temp = []
        for (let i = 0; i < size; i++) {
            const cur: TreeNode = q.shift()
            temp.push(cur.val)
            if (cur.left !== null) q.push(cur.left)
            if (cur.right !== null) q.push(cur.right)
        }
    }
    for (let i = 0; i < temp.length; i++) {
        sum += temp[i]
    }
    return sum
};
  • 这里我新定义了一个temp,while每次循环都意味着开始遍历下一层。
  • 循环过程中是无法确认有没有下一层的,所以每次循环的时候都把temp更新一下。
  • 最后得到的temp中的数据就是二叉树中最深层的数据,我们把他们相加得到的就是答案。

思路二:深度优先遍历

  • 深度优先遍历有点像回溯,有时候leetcode的每日一题会专门给我们练习回朔+减枝来练习各种题目,有兴趣的小伙伴现在可以去专门找来做做,还是蛮有意思的。
  • 先上JavaScript代码看一下大致思路。
// 深度优先(二叉树)
function dfs(node) {
  let list = []
  help(node, [])
  function help(root, temp) {
    if (root.left == null && root.right == null) {// 这一步可以换成其他条件
      const pushTemp = temp.slice()
      pushTemp.push(root.val)
      list.push(pushTemp)
      return
    }
    temp.push(root.val)
    if (root.left) {
      help(root.left, temp)
    }
    if (root.right) {
      help(root.right, temp)
    }
    temp.pop()
  }
}
  • 这里的关键在于help函数中的temp参数,每次递归只要他存在子节点就可以一直递归下去,并且每次都会把当前节点的数据push到temp中,一直到叶子结点的时候,会把temp pop一次,删除最后一个数据,之后重新加入其他节点信息。
  • list最后得到的数据就是从根节点出发,一直到每个叶子结点的路径。
  • root.left == null && root.right == null这个条件在会溯的时候可以根据题目换成别的条件,因为在二叉树里是需要找到left和right都为null的叶子结点再返回,所以二叉树里的条件便是这个。
  • 使用TypeScript根基题目改写代码:
/**
 * Definition for a binary tree node.
 * class TreeNode {
 *     val: number
 *     left: TreeNode | null
 *     right: TreeNode | null
 *     constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
 *         this.val = (val===undefined ? 0 : val)
 *         this.left = (left===undefined ? null : left)
 *         this.right = (right===undefined ? null : right)
 *     }
 * }
 */

function deepestLeavesSum(root: TreeNode | null): number {
    let list:Array<Array<number>> = [], maxDeep:number = 0, sum: number = 0
    help(root, [])
    function help(root: TreeNode | null, temp: number[]) {
        if (root.left == null && root.right == null) {
            const pushTemp = temp.slice()
            pushTemp.push(root.val)
            list.push(pushTemp)
            let deep:number = temp.length
            if (deep > maxDeep) {
                sum = pushTemp[pushTemp.length - 1]
                maxDeep = deep
            } else if (deep === maxDeep) {
                sum += pushTemp[pushTemp.length - 1]
            }
            return
        }
        temp.push(root.val)
        if (root.left) {
            help(root.left, temp)
        }
        if (root.right) {
            help(root.right, temp)
        }
        temp.pop()
    }
    return sum
};
  • 根据这个思路,在找到页面结点后,temp的数组长度就是该叶子结点的深度。为了消除堆的问题影响其他递归的时候对temp的影响,我们把temp浅拷贝到pushTemp中。
  • pushTemp数组的长度就是该叶子结点的深度,我们找到最深的几个叶子结点并相加,就可以得到答案了。

总结

  • 就这道题目来说明显广度优先遍历会更加适合。
  • 回溯的思路可以用来解决非常非常多的问题,不仅仅是二叉树,很多题目可以先把他转化为n叉树,然后用回溯的思想来解决。
  • 广度优先遍历非常耗空间,很多时候是牺牲空间优化时间的一个解决方案,有时候要慎用,广度优先很容易爆栈,当然在这道题目里没有这个问题。