Leetcode 513.找树左下角的值

题目:513.找树左下角的值

初始思路

利用层序遍历模板可以套出来

代码

  1. var findBottomLeftValue = function (root) {
  2. if (!root) return null
  3. let queue = []
  4. queue.push(root)
  5. let res
  6. while (queue.length) {
  7. let length = queue.length
  8. for (let i = 0; i < length; i++){
  9. let node = queue.shift()
  10. if (i === 0) {
  11. res = node.val
  12. }
  13. if (node.left) {
  14. queue.push(node.left)
  15. }
  16. if (node.right) {
  17. queue.push(node.right)
  18. }
  19. }
  20. }
  21. return res
  22. };
  1. var findBottomLeftValue = function (root) {
  2. let maxPath = 0, resNode = null
  3. const dfsTree = function (node, curPath) {
  4. if (node.left == null && node.right == null) {
  5. if (curPath > maxPath) {
  6. // 更新最大深度
  7. maxPath = curPath
  8. // 最大深度最左边的数值
  9. resNode = node.val
  10. }
  11. }
  12. // 单层递归逻辑
  13. if (node.left) {
  14. dfsTree(node.left, curPath + 1)
  15. }
  16. if (node.right) {
  17. dfsTree(node.right, curPath + 1)
  18. }
  19. }
  20. dfsTree(root, 1)
  21. return resNode
  22. }

感想

  1. 这题层序遍历模板比递归简单。递归需要考虑如何找到最下面一层。然后再找到最左边的节点。

Leetcode 112.路径总和

题目:112.路径总和 讲解:https://www.bilibili.com/video/BV19t4y1L7CR

初始思路

之前有道题是求所有路径,对所有路径再求和的话也可以写出来,但是可能会多遍历一些不必要的路径。

代码

  1. var hasPathSum = function (root, targetSum) {
  2. const traversal = function (node, cnt) {
  3. // 结束条件
  4. if (cnt === 0 && node.left == null && node.right== null) {
  5. // 如果遇到叶子节点的时候并且计数为0,说明正好是我们需要的路径
  6. return true
  7. }
  8. if (cnt !== 0 && node.left == null && node.right == null) {
  9. // 遇到了叶子节点,但是计数不为0,直接返回
  10. return false
  11. }
  12. // 左(空节点不遍历).遇到叶子节点返回true,则直接返回true
  13. if (node.left != null && traversal(node.left, cnt - node.left.val)) {
  14. return true
  15. }
  16. // 右(空节点不遍历)
  17. if (node.right != null && traversal(node.right, cnt - node.right.val)) {
  18. return true
  19. }
  20. return false
  21. }
  22. if (!root) return false
  23. // 把目标值传进去,每经过一个节点就减去这个节点的值,如果最后是0的话说明是我们要的路径。不采用累加的做法
  24. return traversal(root, targetSum - root.val)
  25. };
  1. // 迭代
  2. var hasPathSum = function (root, targetSum) {
  3. if (!root) return false
  4. let nodeArr = [root]
  5. let valArr = [0]
  6. while (nodeArr.length) {
  7. let curNode = nodeArr.shift()
  8. let curVal = valArr.shift()
  9. curVal += curNode.val
  10. // 为叶子结点,且和等于目标数,返回true
  11. if (curVal === targetSum && curNode.left == null && curNode.right == null) {
  12. return true
  13. }
  14. // 左
  15. if (curNode.left) {
  16. // 放入下一个左节点
  17. nodeArr.push(curNode.left)
  18. // 放入当前路径的和
  19. valArr.push(curVal)
  20. }
  21. // 右
  22. if (curNode.right) {
  23. // 放入下一个左节点
  24. nodeArr.push(curNode.right)
  25. // 放入当前路径的和
  26. valArr.push(curVal)
  27. }
  28. }
  29. return false
  30. }

感想

  1. 递归的做法更容易理解。采用传入目标值,然后经过路径的时候相减。最后等于0的时候说明是要找的路径
  2. 迭代法利用的是求和累加,每路过一个节点就加上这个节点的值。

Leetcode 113.路径总和 II

题目:113.路径总和 II

初始思路

相比上一题直接返回布尔值,这题需要返回满足条件的路径。需要同时记录下路径。

代码

  1. var pathSum = function (root, targetSum) {
  2. const res = []
  3. const traversal = function (node, cnt, path) {
  4. // 判断路径
  5. if (cnt === 0 && node.left == null && node.right == null) {
  6. // 因为path是一个数组,所以需要解构赋值之后把值放进去
  7. res.push([...path])
  8. return
  9. }
  10. if (cnt !== 0 && node.left == null && node.right == null) {
  11. // 遇到了叶子节点,但是计数不为0
  12. return
  13. }
  14. // 左 (空节点不遍历)
  15. if (node.left) {
  16. path.push(node.left.val)
  17. // 递归
  18. traversal(node.left, cnt - node.left.val, path)
  19. // 回溯
  20. path.pop()
  21. }
  22. // 右 (空节点不遍历)
  23. if (node.right) {
  24. path.push(node.right.val)
  25. // 递归
  26. traversal(node.right, cnt - node.right.val, path)
  27. // 回溯
  28. path.pop()
  29. }
  30. return
  31. }
  32. if (!root) return res
  33. // 把根节点的值减掉当做初始值。把根节点放入路径中
  34. traversal(root, targetSum - root.val, [root.val])
  35. return res
  36. };
  1. // 迭代
  2. var pathSum = function (root, targetSum) {
  3. if (!root) return []
  4. let nodeArr = [root]
  5. let resArr = [] // 记录符合目标和的返回路径
  6. let tempArr = [[]] // 对应路径
  7. let countArr = [0] //对应和
  8. while (nodeArr.length) {
  9. let curNode = nodeArr.shift()
  10. let curVal = countArr.shift()
  11. let path = tempArr.shift()
  12. curVal += curNode.val
  13. path.push(curNode.val)
  14. // 为叶子结点,且和等于目标数,将此次结果数组push进返回数组中
  15. if (curVal === targetSum && curNode.left == null && node.right == null) {
  16. resArr.push(path)
  17. }
  18. // 左,将当前的和及对应路径也对应记录下来
  19. if (curNode.left) {
  20. nodeArr.push(curNode.left)
  21. countArr.push(curVal)
  22. tempArr.push([...path])
  23. }
  24. // 右,将当前的和及对应路径也对应记录下来
  25. if (curNode.right) {
  26. nodeArr.push(curNode.right)
  27. countArr.push(curVal)
  28. tempArr.push([...path])
  29. }
  30. }
  31. return resArr
  32. }

感想

  1. 当在遍历的时候走到叶子节点的时候需要回溯,回到父节点。回溯有些没理解。
  2. 图示:
    image.png
    image.png

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

题目:106.从中序与后序遍历序列构造二叉树 讲解:https://www.bilibili.com/video/BV1vW4y1i7dn

初始思路

数据结构经典问题,根据中序和后序构造二叉树。
因为后序是左右中,所以可以从最后一个节点获得根节点,然后再中序中找到这个根节点,确定左右节点分别是哪些。

代码

  1. var buildTree = function (inorder, postorder) {
  2. if (!inorder.length) return null
  3. // 从后序遍历的数组中获取中间节点的值, 即数组最后一个值
  4. const rootVal = postorder.pop()
  5. // 获取中间节点在中序中的下标
  6. let rootIndex = inorder.indexOf(rootVal)
  7. // 创建中间节点
  8. const root = new TreeNode(rootVal)
  9. // 构造左子树
  10. root.left = buildTree(inorder.slice(0, rootIndex), postorder.slice(0, rootIndex))
  11. // 构造右子树
  12. // 注意在前序数组切割中避开中间的元素
  13. root.right = buildTree(inorder.slice(rootIndex + 1), postorder.slice(rootIndex))
  14. return root
  15. };

感想

  1. 思路简单但是代码不好写。
  2. 学会切割!

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

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

初始思路

和上一题一样的思路,只不过前序的顺序是中左右,所以中节点出现在第一个元素,而后序遍历的中节点出现在最后一个元素。

代码

  1. var buildTree = function (preorder, inorder) {
  2. if (!preorder.length) return null
  3. // 从后序遍历的数组中获取中间节点的值, 即数组第一个值。
  4. const rootVal = preorder.shift()
  5. // 获取中间节点在中序中的下标
  6. let rootIndex = inorder.indexOf(rootVal)
  7. // 创建中间节点
  8. const root = new TreeNode(rootVal)
  9. // 构造左子树
  10. root.left = buildTree(preorder.slice(0, rootIndex), inorder.slice(0, rootIndex))
  11. // 构造右子树
  12. // 注意在中序数组切割中避开中间的元素
  13. root.right = buildTree(preorder.slice(rootIndex), inorder.slice(rootIndex + 1))
  14. return root
  15. };

感想

  1. pop和shift方法都会操作原数组,此时数组已经改变了。
  2. 注意切割的方法,slice(start,end)不包括end,所以是左闭右开。
  3. 如果给前序和后序是不能构造出二叉树的,必须要有中序来确定左子树和右子树。