训练营day18

文章介绍了如何在二叉树中寻找最后一行最左边的值,提供了递归和层序遍历两种方法。此外,还讨论了判断二叉树中是否存在和为目标值的路径问题,同样通过递归和迭代实现。最后提到了从遍历序列构造二叉树的问题,以及寻找所有路径总和等于目标和的路径的解决方案。
摘要由CSDN通过智能技术生成

513.找树左下角的值

力扣题目链接(opens new window)

给定一个二叉树,在树的最后一行找到最左边的值。

示例 1:

513.找树左下角的值

示例 2:

513.找树左下角的值1

var findBottomLeftValue = function(root) {
    let queue = [root];
    let res = root.val;
    while (queue.length) {
        let node = queue.shift();
        if (node.right) queue.push(node.right);
        if (node.left) queue.push(node.left);
        if (!queue.length) res = node.val;
        // if(!node.left && !node.right) res=node.val
    }
    return res;
}
// // 代码随想录
// 递归版本:
var findBottomLeftValue = function(root) {
    //首先考虑递归遍历 前序遍历 找到最大深度的叶子节点即可
    let maxPath = 0, resNode = null;
    // 1. 确定递归函数的函数参数
    const dfsTree = function(node, curPath) {
    // 2. 确定递归函数终止条件
        if(node.left === null && node.right === null) {
            if(curPath > maxPath) {
            maxPath = curPath;
            resNode = node.val;
            }
        }
        node.left && dfsTree(node.left, curPath+1);
        node.right && dfsTree(node.right, curPath+1);
    }
    dfsTree(root,1);
    return resNode;
};
// 层序遍历:
var findBottomLeftValue = function(root) {
    //考虑层序遍历 记录最后一行的第一个节点
    let queue = [];
    if(root === null) { 
        return null;
    }
    queue.push(root);
    let resNode;
    while(queue.length) {
        let length = queue.length;
        for(let i = 0; i < length; i++) {
            let node = queue.shift();
            if(i === 0) {
                resNode = node.val;
            }
            node.left && queue.push(node.left);
            node.right && queue.push(node.right);
        }
    }
    return resNode;
};

 

#112. 路径总和

力扣题目链接(opens new window)

给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。

说明: 叶子节点是指没有子节点的节点。

示例:  给定如下二叉树,以及目标和 sum = 22,

112.路径总和1

返回 true, 因为存在目标和为 22 的根节点到叶子节点的路径 5->4->11->2。

#

 

递归

可以使用深度优先遍历的方式(本题前中后序都可以,无所谓,因为中节点也没有处理逻辑)来遍历二叉树

  1. 确定递归函数的参数和返回类型

参数:需要二叉树的根节点,还需要一个计数器,这个计数器用来计算二叉树的一条边之和是否正好是目标和,计数器为int型。

再来看返回值,递归函数什么时候需要返回值?什么时候不需要返回值?这里总结如下三点:

  • 如果需要搜索整棵二叉树且不用处理递归返回值,递归函数就不要返回值。(这种情况就是本文下半部分介绍的113.路径总和ii)
  • 如果需要搜索整棵二叉树且需要处理递归返回值,递归函数就需要返回值。 (这种情况我们在236. 二叉树的最近公共祖先 (opens new window)中介绍)
  • 如果要搜索其中一条符合条件的路径,那么递归一定需要返回值,因为遇到符合条件的路径了就要及时返回。(本题的情况)

而本题我们要找一条符合条件的路径,所以递归函数需要返回值,及时返回,那么返回类型是什么呢?

如图所示:

112.路径总和 

 

let haspathsum = function(root, targetsum) {
    if (!root) return false;
    if (!root.left && !root.right) return targetsum === root.val;
    return haspathsum(root.left, targetsum - root.val) || haspathsum(root.right, targetsum - root.val);
};
// 代码随想录
// 递归法
let haspathsum = function (root, targetsum) {
    // 递归法
    const traversal = (node, cnt) => {
      // 遇到叶子节点,并且计数为0
      if (cnt === 0 && !node.left && !node.right) return true;
      // 遇到叶子节点而没有找到合适的边(计数不为0),直接返回
      if (!node.left && !node.right) return false;
  
      //  左(空节点不遍历).遇到叶子节点返回true,则直接返回true
      if (node.left && traversal(node.left, cnt - node.left.val)) return true;
      //  右(空节点不遍历)  
      if (node.right && traversal(node.right, cnt - node.right.val)) return true;
      return false;
    };
    if (!root) return false;
    return traversal(root, targetsum - root.val);
  
    // 精简代码:
    // if (!root) return false;
    // if (!root.left && !root.right && targetsum === root.val) return true;
    // return haspathsum(root.left, targetsum - root.val) || haspathsum(root.right, targetsum - root.val);
};
// 迭代
let hasPathSum = function(root, targetSum) {
    if(root === null) return false;
    let nodeArr = [root];
    let valArr = [0];
    while(nodeArr.length) {
        let curNode = nodeArr.shift();
        let curVal = valArr.shift();
        curVal += curNode.val;
        // 为叶子结点,且和等于目标数,返回true
        if (curNode.left === null && curNode.right === null && curVal === targetSum) {
            return true;
        }
        // 左节点,将当前的数值也对应记录下来
        if (curNode.left) {
            nodeArr.push(curNode.left);
            valArr.push(curVal);
        }
        // 右节点,将当前的数值也对应记录下来
        if (curNode.right) {
            nodeArr.push(curNode.right);
            valArr.push(curVal);
        }
    }
    return false;
};

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

力扣题目链接(opens new window)

根据一棵树的中序遍历与后序遍历构造二叉树。

注意: 你可以假设树中没有重复的元素。

例如,给出

中序遍历 inorder = [9,3,15,20,7] 后序遍历 postorder = [9,15,7,20,3] 返回如下的二叉树:

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

 

var buildTree=function(inorder,postorder){
    if(!inorder.length) return null;
    const rootVal=postorder.pop();
    const index=inorder.indexOf(rootVal);
    const root=new TreeNode(rootVal);
    root.left=buildTree(inorder.slice(0,index),postorder.slice(0,index));
    root.right=buildTree(inorder.slice(index+1),postorder.slice(index));
    return root;
}
//代码随想录
var buildTree = function(inorder, postorder) {
    if (!inorder.length) return null;
    const rootVal = postorder.pop(); // 从后序遍历的数组中获取中间节点的值, 即数组最后一个值
    let rootIndex = inorder.indexOf(rootVal); // 获取中间节点在中序遍历中的下标
    const root = new TreeNode(rootVal); // 创建中间节点
    root.left = buildTree(inorder.slice(0, rootIndex), postorder.slice(0, rootIndex)); // 创建左节点
    root.right = buildTree(inorder.slice(rootIndex + 1), postorder.slice(rootIndex)); // 创建右节点
    return root;
};

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

力扣题目链接(opens new window)

根据一棵树的前序遍历与中序遍历构造二叉树。

注意: 你可以假设树中没有重复的元素。

例如,给出

前序遍历 preorder = [3,9,20,15,7] 中序遍历 inorder = [9,3,15,20,7] 返回如下的二叉树:

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

 

var buildTree = function(preorder, inorder) {
    if (preorder.length === 0) return null;
    let root = new TreeNode(preorder[0]);
    let index = inorder.indexOf(preorder[0]);
    root.left = buildTree(preorder.slice(1, index + 1), inorder.slice(0, index));
    root.right = buildTree(preorder.slice(index + 1), inorder.slice(index + 1));
    return root;
};
// 代码随想录
// 从前序与中序遍历序列构造二叉树
var buildTree = function(preorder, inorder) {
    if (!preorder.length) return null;
    const rootVal = preorder.shift(); // 从前序遍历的数组中获取中间节点的值, 即数组第一个值
    const index = inorder.indexOf(rootVal); // 获取中间节点在中序遍历中的下标
    const root = new TreeNode(rootVal); // 创建中间节点
    root.left = buildTree(preorder.slice(0, index), inorder.slice(0, index)); // 创建左节点
    root.right = buildTree(preorder.slice(index), inorder.slice(index + 1)); // 创建右节点
    return root;
  };

113. 路径总和ii

力扣题目链接(opens new window)

给定一个二叉树和一个目标和,找到所有从根节点到叶子节点路径总和等于给定目标和的路径。

说明: 叶子节点是指没有子节点的节点。

示例: 给定如下二叉树,以及目标和 sum = 22,

113.路径总和ii1.png

#思路

113.路径总和ii要遍历整个树,找到所有路径,所以递归函数不要返回值!

如图:

113.路径总和ii

// // 代码随想录
let pathsum = function (root, targetsum) {
    // 递归法
    // 要遍历整个树找到所有路径,所以递归函数不需要返回值, 与112不同
    const res = [];
    const travelsal = (node, cnt, path) => {
      // 遇到了叶子节点且找到了和为sum的路径
      if (cnt === 0 && !node.left && !node.right) {
        res.push([...path]); // 不能写res.push(path), 要深拷贝
        return;
      }
      if (!node.left && !node.right) return; // 遇到叶子节点而没有找到合适的边,直接返回
      // 左 (空节点不遍历)
      if (node.left) {
        path.push(node.left.val);
        travelsal(node.left, cnt - node.left.val, path); // 递归
        path.pop(); // 回溯
      }
      // 右 (空节点不遍历)
      if (node.right) {
        path.push(node.right.val);
        travelsal(node.right, cnt - node.right.val, path); // 递归
        path.pop(); // 回溯
      }
      return;
    };
    if (!root) return res;
    travelsal(root, targetsum - root.val, [root.val]); // 把根节点放进路径
    return res;
  };
//   递归 精简版
var pathsum = function(root, targetsum) {
    //递归方法
    let respath = [],curpath = [];
    // 1. 确定递归函数参数
    const traveltree = function(node,count) {
        curpath.push(node.val);
        count -= node.val;
        if(node.left === null && node.right === null && count === 0) {
            respath.push([...curpath]);
        }
        node.left && traveltree(node.left, count);
        node.right && traveltree(node.right, count);
        let cur = curpath.pop();
        count -= cur;
    }
    if(root === null) {
        return respath;
    }
    travelTree(root, targetSum);
    return resPath;
};
// 迭代
let pathSum = function(root, targetSum) {
    if(root === null) return [];
    let nodeArr = [root];
    let resArr = []; // 记录符合目标和的返回路径
    let tempArr = [[]]; // 对应路径
    let countArr = [0]; //对应和
    while(nodeArr.length) {
        let curNode = nodeArr.shift();
        let curVal = countArr.shift();
        let curNodeArr = tempArr.shift();
        curVal += curNode.val;
        curNodeArr.push(curNode.val);
        // 为叶子结点,且和等于目标数,将此次结果数组push进返回数组中
        if (curNode.left === null && curNode.right === null && curVal === targetSum) {
            resArr.push(curNodeArr);
        }
        // 左节点,将当前的和及对应路径也对应记录下来
        if (curNode.left) {
            nodeArr.push(curNode.left);
            countArr.push(curVal);
            tempArr.push([...curNodeArr]);
        }
         // 右节点,将当前的和及对应路径也对应记录下来
        if (curNode.right) {
            nodeArr.push(curNode.right);
            countArr.push(curVal);
            tempArr.push([...curNodeArr]);
        }
    }
    return resArr;
};

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值