Day15-Day16-二叉树-leetcode110,257,404,222 - 513,112,113,106

  1. 平衡二叉树
  • 给定一个二叉树,判断它是否是 平衡二叉树

  • 思路

  • 平衡二叉树,一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。

  • 深度,从上到下去查,需要前序遍历(中左右),而高度,从下到上去查,只能后序遍历(左右中)

  • 递归三步曲分析:

    1. 明确递归函数的参数和返回值:参数:当前传入节点。 返回值:以当前传入节点为根节点的树的高度
    1. 明确终止条件:递归的过程中依然是遇到空节点了为终止,返回0,表示当前节点为根节点的树高度为0
    1. 明确单层递归的逻辑:如何判断以当前传入节点为根节点的二叉树是否是平衡二叉树呢?当然是其左子树高度和其右子树高度的差值。分别求出其左右子树的高度,然后如果差值小于等于1,则返回当前二叉树的高度,否则返回-1,表示已经不是二叉平衡树了。
  • 深度适合用前序遍历,而高度适合用后序遍历。

var isBalanced = function(root) {
    //还是用递归三部曲 + 后序遍历 左右中 当前左子树右子树高度相差大于1就返回-1
    // 1. 确定递归函数参数以及返回值
    const getDepth = function(node) {
        // 2. 确定递归函数终止条件
        if(node === null) return 0;
        // 3. 确定单层递归逻辑
        let leftDepth = getDepth(node.left); //左子树高度
        // 当判定左子树不为平衡二叉树时,即可直接返回-1
        if(leftDepth === -1) return -1;
        let rightDepth = getDepth(node.right); //右子树高度
        // 当判定右子树不为平衡二叉树时,即可直接返回-1
        if(rightDepth === -1) return -1;
        if(Math.abs(leftDepth - rightDepth) > 1) {
            return -1;
        } else {
            return 1 + Math.max(leftDepth, rightDepth);
        }
    }
    return !(getDepth(root) === -1);
};

  1. 二叉树的所有路径
  • 给你一个二叉树的根节点 root ,按 任意顺序 ,返回所有从根节点到叶子节点的路径。 叶子节点 是指没有子节点的节点。

  • 思路

  • 从根节点到叶子的路径,所以需要前序遍历,方便让父节点指向孩子节点,找到对应的路径。

// 递归
var binaryTreePaths = function(root) {
   //递归遍历+递归三部曲
    // res 用于保存所有路径的结果(字符串数组)。
    // getPath 是递归辅助函数,参数是当前节点 node 和当前路径字符串 curPath。
    // 从根节点和空路径开始递归。
   let res = [];
   //1. 确定递归函数 函数参数
   const getPath = function(node,curPath) {
    //2. 确定终止条件,到叶子节点就终止
    // 如果当前节点是叶子节点(左右孩子都为空),
   // 把当前节点值拼接到路径末尾,并把完整路径加入结果数组 res,然后返回。
       if(node.left === null && node.right === null) {
           curPath += node.val;
           res.push(curPath);
           return;
       }
       //3. 确定单层递归逻辑
        // 不是叶子节点时,把当前节点值和 "->" 拼接到路径字符串后面。
        // 如果有左孩子,递归左孩子;有右孩子,递归右孩子。
       curPath += node.val + '->';
       node.left && getPath(node.left, curPath);
       node.right && getPath(node.right, curPath);
   }
   getPath(root, '');
   return res;
};

  1. 左叶子之和
  • 给定二叉树的根节点 root ,返回所有左叶子之和。

  • 思路

  • 节点A的左孩子不为空,且左孩子的左右孩子都为空(说明是叶子节点),那么A节点的左孩子为左叶子节点。通过节点的父节点来判断其左孩子是不是左叶子了。

// 迭代
var sumOfLeftLeaves = function(root) {
   //采用层序遍历
   if(root === null) {
       return null;
   }
   let queue = [];
   let sum = 0;
   queue.push(root);
   while(queue.length) {
     let node = queue.shift();
     if(node.left !== null && node.left.left === null && node.left.right === null) {
         sum+=node.left.val;
     }
     node.left && queue.push(node.left);
     node.right && queue.push(node.right);
   }
   return sum;
};

  1. 完全二叉树的节点个数
  • 给你一棵 完全二叉树 的根节点 root ,求出该树的节点个数。

  • 完全二叉树 的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层(从第 0 层开始),则该层包含 1~ 2h 个节点。

  • 思路:

  • 普通二叉树的节点数
  • 递归法
    1. 确定递归函数的参数和返回值:参数就是传入树的根节点,返回就返回以该节点为根节点二叉树的节点数量
    1. 确定终止条件:如果为空节点的话,就返回0,表示节点数为0。
    1. 确定单层递归的逻辑:先求它的左子树的节点数量,再求右子树的节点数量,最后取总和再加一 (加1是因为算上当前中间节点)就是目前节点为根节点的节点数量。
var countNodes = function(root) {
    //递归法计算二叉树节点数 后序遍历
    // 1. 确定递归函数参数
    const getNodeSum = function(node) {
    //2. 确定终止条件
        if(node === null) {
            return 0;
        }
    //3. 确定单层递归逻辑
        let leftNum = getNodeSum(node.left);
        let rightNum = getNodeSum(node.right);
        return leftNum + rightNum + 1;
    }
    return getNodeSum(root);
};
// 迭代,层序遍历
var countNodes = function(root) {
    let queue = []
    if (root === null) {
        return 0
    }
    queue.push(root)
    let nodeNums = 0
    while(queue.length) {
        let len = queue.length
        while(len--) {
            let node = queue.shift()
            nodeNums++
            node.left && queue.push(node.left)
            node.right && queue.push(node.right)
        }
    }
    return nodeNums
}
  • 完全二叉树
    • 完全二叉树只有两种情况,情况一:就是满二叉树,2^树深度 - 1 ;情况二:最后一层叶子节点没有满,分别递归左孩子,和右孩子,递归到某一深度一定会有左孩子或者右孩子为满二叉树,然后依然可以按照情况1来计算。
var countNodes = function(root) {
    if(root === null) {
        return 0
    }
    let left = root.left
    let right = root.right
    let leftDepth = 0, rightDepth = 0
    while(left) {
        left = left.left
        leftDepth++
    }
    while(right){
        right = right.right
        rightDepth++
    }
    if(leftDepth == rightDepth) {
        return Math.pow(2, leftDepth+1) - 1
    }
    return countNodes(root.left) + countNodes(root.right) + 1
}

  1. 找树左下角的值
  • 给定一个二叉树的 根节点 root,请找出该二叉树的 最底层 最左边 节点的值。

  • 假设二叉树中至少有一个节点。

  • 思路

  • 找出树的最后一行的最左边的值。

// 迭代,层序遍历
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;
};

  1. 路径总和
  • 给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。如果存在,返回 true ;否则,返回 false 。

  • 叶子节点 是指没有子节点的节点。

  • 思路

  • 递归

  • 不要去累加然后判断是否等于目标和,那么代码比较麻烦,可以用递减,让计数器count初始为目标和,然后每次减去遍历路径节点上的数值。

  • 如果最后count == 0,同时到了叶子节点的话,说明找到了目标和。

  • 如果遍历到了叶子节点,count不为0,就是没找到。

// 递归 
let hasPathSum = function(root, targetSum) {
  if (!root) return false;
  // 到叶子节点且和正好等于targetSum
  if (!root.left && !root.right && root.val === targetSum) 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;
    // nodeArr 用于存储待遍历的节点(队列)。
    // valArr 用于存储到当前节点为止的路径和(与节点一一对应)。
    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;
};

  1. 路径总和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;
};

  1. 从中序与后序遍历序列构造二叉树
  • 给定两个整数数组 inorder 和 postorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。

  • 思路

  • 递归

  • 第一步:如果数组大小为零的话,说明是空节点了。

  • 第二步:如果不为空,那么取后序数组最后一个元素作为节点元素。

  • 第三步:找到后序数组最后一个元素在中序数组的位置,作为切割点

  • 第四步:切割中序数组,切成中序左数组和中序右数组 (顺序别搞反了,一定是先切中序数组)

  • 第五步:切割后序数组,切成后序左数组和后序右数组

  • 第六步:递归处理左区间和右区间

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;
};

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;
};



参考&感谢各路大神

posted @ 2025-06-11 22:45  安静的嘶吼  阅读(4)  评论(0)    收藏  举报