513 找树左下角的值
题目链接/文章讲解/视频讲解:https://programmercarl.com/0513.%E6%89%BE%E6%A0%91%E5%B7%A6%E4%B8%8B%E8%A7%92%E7%9A%84%E5%80%BC.html
方法一:BFS
直接想到的就是这种方法,返回最后一层的第一个元素[ [1],[2,3],[4,5,6],[7] ],一开始以为这样不能通过,因为下图这种情况,但是居然通过了,那就是这种情况也是最底层最左侧的
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number}
*/
var findBottomLeftValue = function(root) {
let res = [];
let queue = [root];
while(queue.length){
let len = queue.length;
let subRes = [];
while(len){
let cur = queue.shift();
subRes.push(cur.val);
if(cur.left) queue.push(cur.left);
if(cur.right) queue.push(cur.right);
len--;
}
res.push(subRes);
}
return res[res.length-1][0];
};
112 路径总和
题目链接/文章讲解/视频讲解:https://programmercarl.com/0112.%E8%B7%AF%E5%BE%84%E6%80%BB%E5%92%8C.html
方法一:DFS递归,回溯,不是精简版的递归,体现了回溯思想(先减掉再加上,因为每一次遍历到底,也就是更改了cnt的值,如果这条路径不对,返回去需要更改路径,也就是说要把cnt复原)
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @param {number} targetSum
* @return {boolean}
*/
var hasPathSum = function(root, targetSum) {
const traversal = (node,cnt) => {
//确定终止条件
//已经是叶子节点,且总和已经为0,则存在路径
if(node.left === null && node.right === null && cnt === 0){
return true;
}
//已经是叶子节点,但总和不为0,则不存在路径
if(node.left === null && node.right === null && cnt !== 0){
return false;
}
//单层循环逻辑
if(node.left){
cnt -= node.left.val;
if(traversal(node.left,cnt)) return true;
//回溯思想
cnt += node.left.val;
}
if(node.right){
cnt -= node.right.val;
if(traversal(node.right,cnt)) return true;
cnt += node.right.val;
}
return false;
}
if(root === null) return false;
//注意,这里传入的总和要已经减去根节点的值
return traversal(root,targetSum - root.val);
};
113 路径总和Ⅱ
方法一:递归
本题结合了112路径总和和257二叉树的所有路径,更多的是在112的基础上加了一个参数path
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @param {number} targetSum
* @return {number[][]}
*/
var pathSum = function(root, targetSum) {
// 要遍历整个树找到所有路径,所以递归函数不需要返回值, 与112不同
const res = [];
const travelsal = (node,cnt,path) => {
if(cnt === 0 && node.left === null && node.right === null){
res.push([...path]); //不能写res.push(path),要深拷贝
return;
}
if(cnt !== 0 && node.left === null && node.right === null){
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 === null) return [];
//把根节点放进路径
travelsal(root,targetSum - root.val,[root.val]);
return res;
};
106 从中序与后序遍历序列构造二叉树
题目链接/文章讲解/视频讲解:https://programmercarl.com/0106.%E4%BB%8E%E4%B8%AD%E5%BA%8F%E4%B8%8E%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86%E5%BA%8F%E5%88%97%E6%9E%84%E9%80%A0%E4%BA%8C%E5%8F%89%E6%A0%91.html
思路:其实思路核心都是要根据后序来知道根节点的值,然后以此来划分左子树的区间和右子树的区间。
方法一:笨猪爆破组的方法,很详细
方法一:笨猪爆破组
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {number[]} inorder
* @param {number[]} postorder
* @return {TreeNode}
*/
var buildTree = function(inorder, postorder) {
const map = {};
//把inorder数组内的节点值和对应索引先用map存起来,方便找mid的位置
for(let i = 0;i<inorder.length;i++){
map[inorder[i]] = i;
}
const helper = (iStart,iEnd,pStart,pEnd) => {
//指针交错了,返回null节点
if(pStart > pEnd || iStart > iEnd){
return null;
}
//根据后序数组里的最后一个值获取到根节点的值
const rootVal = postorder[pEnd];
//获取到根节点的值在中序数组中的位置,方便划分左子树和右子树
const mid = map[rootVal];
//获取到左子树的节点个数
const leftNodeNum = mid-iStart;
//创建根节点
const root = new TreeNode(rootVal);
//递归构建左子树
root.left = helper(iStart,mid-1,pStart,pStart+leftNodeNum-1);
//递归构建右子树
root.right = helper(mid+1,iEnd,pStart+leftNodeNum,pEnd-1);
return root;
}
return helper(0,inorder.length-1,0,postorder.length-1);
};
方法二:代码随想录
笨猪爆破组的方法没有使用indexof,而是使用了map,用空间换时间,所以时间复杂度很低,而代码随想录的方法由于使用了indexof所以时间复杂度很高。
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {number[]} inorder
* @param {number[]} postorder
* @return {TreeNode}
*/
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 从前序与中序遍历序列构造二叉树
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {number[]} preorder
* @param {number[]} inorder
* @return {TreeNode}
*/
var buildTree = function(preorder, inorder) {
const map = {};
for(let i = 0;i<inorder.length;i++){
map[inorder[i]] = i;
}
const helper = (pStart,pEnd,iStart,iEnd) => {
if(pStart > pEnd || iStart > iEnd){
return null;
}
let rootVal = preorder[pStart];
let root = new TreeNode(rootVal);
let mid = map[rootVal];
let leftNodeNum = mid - iStart;
root.left = helper(pStart+1,pStart+leftNodeNum,iStart,mid-1);
root.right = helper(pStart+leftNodeNum+1,pEnd,mid+1,iEnd);
return root;
}
return helper(0,preorder.length-1,0,inorder.length-1);
};
思路和106一样,只是这次自己在写root.left和root.right的时候,觉得leftNodeNum和mid不应该是一样的吗,所以不太确定,虽然最后也还是没弄明白为什么两者不能替换,补充补充:一开始我之所以以为mid和leftNodeNum是一样的是因为我一直拿iStart=0,但之后的递归iStart并不一直是0,递归的过程还是有点迷糊。
在构建root.left时,我们应该使用leftNum而不是mid来确定左子树的范围。同样,在构建root.right时,应该使用mid+1而不是leftNum+1来确定右子树的范围,以确保正确的划分左右子树。