路径总和

143 阅读2分钟

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

一、题目描述:

是否存在从当前节点 root 到叶子节点的路径,满足其路径和为 sum

截屏2022-03-11 上午7.26.41.png

let root = {
  value: 5,
  left: {
    value: 4,
    left: {
      value: 11,
      left: {
        value: 7,
        left: null,
        right: null
      },
      right: {
        value: 2,
        left: null,
        right: null
      }
    },
    right: null
  },
  right: {
    value: 8,
    left: {
      value: 13,
      left: null,
      right: null
    },
    right: {
      value: 4,
      left: null,
      right: {
        value: 1,
        left: null,
        right: null
      }
    }
  }
}

当 target=22 时,路径为 5,4,11,2

二、思路分析:

题目给我们的是一个二叉树,我们需要想办法遍历二叉树,关于遍历二叉树有有3种方式:

  1. 中序遍历按照节点上的键值,以升序访问二叉树上的所有节点。
  2. 先序遍历先访问根节点,然后以同样方式访问左子树和右子树。
  3. 后序遍历先访问叶子节点,从左子树到右子树,再到根节点。

根据题目意思:是否存在从当前节点 root 到叶子节点的路径,满足其路径和为 sum。我们需要使用先序遍历来处理,先处理根节点,再处理叶子节点。

// 深度优先
function hasPathSum (root, sum) {
  if (root == null) {
    return false
  }
  if (root.left == null && root.right == null) {
    return root.value === sum
  }
  return hasPathSum(root.left, sum - root.value) || hasPathSum(root.right, sum - root.value)
}

对于一颗二叉树,深度优先搜索(Depth First Search)(其实是二叉树的先序遍历)是沿着树的深度遍历树的节点,尽可能深的搜索树的分支。

关于二叉树的遍历除了使用递归我们还可以使用队列来模拟:

// 广度优先搜索
function hasPathSum (root, sum) {
  if (root === null) {
    return false
  }
  let node = [root]
  let value = [root.value]
  while (node.length) {
    let now = node.shift()
    let temp = value.shift()
    if (temp === sum) {
      return true
    }
    if (now.left && now.left !== null) {
      node.push(now.left)
      value.push(now.left.value + temp)
    }
    if (now.right && now.right !== null) {
      node.push(now.right)
      value.push(now.right.value + temp)
    }
  }
  return false
}

广度优先搜索(Breadth First Search)(其实是二叉树的层次遍历),又叫宽度优先搜索或横向优先搜索,是从根结点开始沿着树的宽度搜索遍历,上面二叉树的遍历顺序为:5,4,8,11,13,4,7,2,1 可以利用队列实现广度优先搜索。

我们再看一个面试题,计算二叉树的所有路径。

function binaryTree (root) {
  let paths = []
  let construct_paths = (root, path) => {
    if (root) {
      path += root.value.toString()
      if (root.left === null && root.right === null) {
        paths.push(path)
      } else {
        // 当前节点不是叶子节点,继续递归
        path += '->'
        construct_paths(root.left, path)
        construct_paths(root.right, path)
      }
    }
  }
  construct_paths(root, '')
  return paths
}
// [ '5->4->11->7', '5->4->11->2', '5->8->13', '5->8->4->1' ]

我们继续使用广度优先搜索重写算法:

function binaryTree (root) {
  if (root === null) {
    return false
  }
  let node = [root]
  let value = [root.value]
  let paths = []
  while (node.length) {
    let now = node.shift()
    let temp = value.shift()
    if (now.left === null && now.right === null) {
      paths.push(temp)
    }
    if (now.left && now.left !== null) {
      node.push(now.left)
      value.push(temp  + '->'+ now.left.value)
    }
    if (now.right && now.right !== null) {
      node.push(now.right)
      value.push(temp  + '->'+ now.right.value)
    }
  }
  return paths
}

这里在提一句,深度优先和广度优先的感念, “深度优先搜索就是树的先序遍历”,“广度优先搜索就是树的按层遍历”。