feat: 二叉树相关题目100,101,104,105,112,114,117,124,129,173,222,226,236

This commit is contained in:
LouisFonda 2025-04-25 14:41:15 +08:00
parent c2c64470b0
commit e7559ec1c7
Signed by: yigencong
GPG Key ID: 29CE877CED00E966
14 changed files with 1200 additions and 0 deletions

View File

@ -0,0 +1,74 @@
/**
* 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)
* }
*/
/**
* https://leetcode.cn/problems/same-tree/?envType=study-plan-v2&envId=top-interview-150
* @param {TreeNode} p
* @param {TreeNode} q
* @return {boolean}
*/
const isSameTree = function (p, q) {
};
/*
如果一个树的字节的和另一个树的子节点相等只要保证当前节点相等的话
那么整棵树也就相等了
*/
function f1(p, q) {
if (p === null && q === null) return true;
if (p && q && p.val === q.val) {
return f1(p.left, q.left) && f1(p.right, q.right);
}
return false;
}
/*
优化判断分支
*/
function f2(p, q) {
// 如果两个节点都为空,返回 true表示当前节点相等
if (!p && !q) return true;
// 如果其中一个节点为空,或者节点值不相等,返回 false
if (!p || !q || p.val !== q.val) return false;
// 递归检查左右子树
return f1(p.left, q.left) && f1(p.right, q.right);
}
/*
通过BFS来判断两个数每一层对应节点的值是否相等
*/
function f3(p, q) {
// 初步判断,如果两个树的根节点有一个为空,另一个不为空,则返回 false
if (!p && !q) return true;
if (!p || !q) return false;
// 上面这两个判断可有可无
const queue = [p, q]; // 队列存储两个树的根节点
while (queue.length > 0) {
const node1 = queue.shift();
const node2 = queue.shift();
if (!node1 && !node2) continue; // 如果两个节点都为空
// 如果两个节点值不同,或者其中一个为空,直接返回 false
if (!node1 || !node2 || node1.val !== node2.val) {
return false;
}
// 将左子节点和右子节点加入队列
queue.push(node1.left, node2.left);
queue.push(node1.right, node2.right);
}
return true;
}

View File

@ -0,0 +1,101 @@
/**
* 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 {boolean}
*/
// const isSymmetric = function (root) {
// return f1(root.left, root.right);
// };
/*
递归1.基础条件如果两个树的根节点为空则它们对称如果一个为空而另一个不为空则不对称如果两个节点的值不相等则不对称
2.递归检查左右子树的对称性左子树的左子树与右子树的右子树对称左子树的右子树与右子树的左子树对称
*/
function f1(left, right) {
// 如果左右两颗树的根节点都为空,则对称
if (!left && !right) return true;
// 上面判断排除了都为空的情况,所以至少有一颗树不为空,如果有的话,表明不对称
if (!left || !right) return false;
// 如果两个节点不为空两个节点的值不相等返回false
if (left.val !== right.val) return false;
// 递归的对称检查left.left和right.right, left.right和righ.left
return f1(left.left, right.right) && f1(left.right, right.left);
}
/*
使用bfs,使用两个队列left和right分别表示镜像每一层的左右节点然后检查left从左到右存节点
right从右到左存节点然后一一比较如果都相当则表明这一层是对称的继续检查下一层如果每一层
都是对称的就表明整颗树都是对称的
*/
function f2(root) {
const queueL = [root.left]; // 遍历镜像左边的节点
const queueR = [root.right]; // 遍历镜像右边的节点
// 每一层的节点个数
let leaveSize = 1;
while (queueL.length > 0) {
console.log('lq: ', queueL);
console.log('rq: ', queueR);
// 判断左右节点
for (let i = 0; i < leaveSize; i++) {
// 取出对应节点比较
const leftSideNode = queueL.shift();
const rightSideNode = queueR.shift();
// 如果两个节点都为空,直接跳过本次循环,根本就没有子节点,无需做后面的操作
if (!leftSideNode && !rightSideNode) continue;
// 如果两个节点一个为空直接返回false
if (!leftSideNode || !rightSideNode) return false;
// 左右两个节点值不相等
if (leftSideNode.val !== rightSideNode.val) return false;
// 将左侧和右侧的节点按照顺序压入栈中
queueL.push(leftSideNode.left);
queueL.push(leftSideNode.right);
queueR.push(rightSideNode.right);
queueR.push(rightSideNode.left);
}
leaveSize = queueL.length;
}
return true;
}
/*
dfs,只需一个循环依此检查即可
*/
function f3(root) {
const stackL = [root.left]; // 遍历镜像左边的节点
const stackR = [root.right]; // 遍历镜像右边的节点
while (stackL.length > 0) {
// 拿出镜像左右两边的节点比较
const leftSideNode = stackL.pop();
const rightSideNode = stackR.pop();
// 如果两个值都为空就跳过
if (!leftSideNode && !rightSideNode) continue;
// 如果两个节点中有一个为null就返回false
if (!leftSideNode || !rightSideNode) return false;
// 如果两个节点都存在但是值不相等返回false
if (leftSideNode.val !== rightSideNode.val) return false;
// 将左右节点压入栈中leftSideNode.right->leftSideNode.left, rightSideNode.left->rightSideNode.right
stackL.push(leftSideNode.right);
stackL.push(leftSideNode.left);
stackR.push(rightSideNode.left);
stackR.push(rightSideNode.right);
}
return true;
}

View File

@ -0,0 +1,49 @@
/**
* 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}
*/
const maxDepth = function (root) {
};
/*
这非常符合递归操作如需直到这颗数的最大深度只需计算左右子树的深度中较大的加上自己的这一层
不就是整颗数的深度吗
*/
function f1(root) {
if (!root) return 0;
return Math.max(f1(root.left), f1(root.right)) + 1;
}
/*
利用BFS来查找每一层的所有节点然后遍历所有每一层的所有节点把查找是否有下一层节点然后
把计数加1直到这一层没有任何节点
*/
function f2(root) {
if (!root) return 0;
const queue = [root]; // 初始化队列,将根节点加入队列
let depth = 0;
while (queue.length > 0) {
const levelSize = queue.length; // 当前层的节点个数
for (let i = 0; i < levelSize; i++) {
const node = queue.shift(); // 从队列中取出一个节点
if (node.left) queue.push(node.left); // 如果左子节点存在,加入队列
if (node.right) queue.push(node.right); // 如果右子节点存在,加入队列
}
depth++; // 每完成一次层级遍历,深度加一
}
return depth;
}

View File

@ -0,0 +1,148 @@
/**
* 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)
* }
*/
/**
* https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/?envType=study-plan-v2&envId=top-interview-150
* @param {number[]} preorder
* @param {number[]} inorder
* @return {TreeNode}
*/
/*
二叉树的问题都能通过递归抽象解决利用前序遍历的结果和中序遍历的结果也可以通过抽象解决
抽象问题以最简单的二叉树[1,2,3]来举例他的中序遍历结果为[2,1,3],前序遍历的第一个结果为
根节点而在中序遍历中根节点恰好把遍历结果分成了左右两部分左侧为根节点左子树的遍历结果
右侧为根节点右子树的遍历结果如果我们能找到根节点左子树的前序遍历结果和右子树的前序遍历结果
就可以递归调用恢复整棵二叉树这一点我们能从根节点的二叉树获取
过程分析
[1,2,4,5,3,6,7]这个二叉树为例根据上面的思路我们可以找到二叉树的根节点值1然后利用中序
遍历结果[4,2,5,1,6,3,7]将二叉树左右子树的中序遍历结果获取到分别为[4,2,5],[6,3,7],仔细观察
前序遍历的结果[1,2,4,5,3,6,7]也能获取左子树的前序遍历那就是从根节点后面的左子树个数个数也就是
根节点后面的三个数右子树的前序遍历同理之后递归调用即可
*/
import TreeNode from './tool';
const buildTree = function (preorder, inorder) {
};
function f1(preorder, inorder) {
if (preorder.length === 0) return null; // 如果子树没有节点直接返回null
// 根据preorder的第一个值在inorder中定位根节点并把遍历结果分成左右子树两部分
const rootIndex = inorder.indexOf(preorder[0]);
const leftInOrder = inorder.slice(0, rootIndex);
const rightInOrder = inorder.slice(rootIndex + 1);
// 根据左子树和右子树的节点个数获取前序遍历的结果
const leftPreOrder = preorder.slice(1, 1 + rootIndex);
const rightPreOrder = preorder.slice(1 + rootIndex);
// 通过子树的前中序遍历结果递归构建左右子树
// const left = f1(leftPreOrder, leftInOrder);
// const right = f1(rightPreOrder, rightInOrder);
// 创建根节点
// const root = new TreeNode(preorder[0], left, right);
// return root;
// 一次性返回上面的过程
return new TreeNode(preorder[0], f1(leftPreOrder, leftInOrder), f1(rightPreOrder, rightInOrder));
}
/*
f1的做法思路非常清晰但是分割数组会消耗大量内存实际做法应该使用分割左右子树这样能节省
大量的空间而且免去了分割数组的复杂度
思考只需传递三个下标第一个指向前序的root,第二个和第三个指向中序的结果左右边界
hard: 右子树的左右边界比较难计算多思考几下
*/
function f2(preorder, inorder, preOrderIndex, inOrderLeft, inOrderRight) {
if (inOrderLeft > inOrderRight) return null;
// 根据前序遍历的第一个结果,也就是根节点的值,将中序遍历分割成左右子树
const rootIndex = inorder.indexOf(preorder[preOrderIndex]);
const left = f2(preorder, inorder, preOrderIndex + 1, inOrderLeft, rootIndex - 1);
// eslint-disable-next-line max-len
const right = f2(preorder, inorder, preOrderIndex + (rootIndex - inOrderLeft) + 1, rootIndex + 1, inOrderRight);
return new TreeNode(preorder[preOrderIndex], left, right);
}
/*
上面代码已经十分简洁了但是还有一个可以优化的地方就是indexOf这个找过程题目中所给的测试
用例值是没有重复的所以我们可以建立一个hashMap前序遍历的值作为下标前序遍历的下标作为值
到时候查早下标只需O(1)的时间复杂度
*/
function f3(preO, inO) {
// 构建map映射
const map = new Map();
for (let i = 0; i < inO.length; i++) {
map.set(inO[i], i);
}
return build(preO, inO, 0, 0, inO.length - 1);
/**
*
* @param {*} preorder 前序遍历结果
* @param {*} inorder 中序遍历结果
* @param {*} preOrderIndex 前序遍历的根节点值
* @param {*} inOrderLeft 中序遍历的左边界
* @param {*} inOrderRight 中序遍历的右边界
* @returns
*/
function build(preorder, inorder, preOrderIndex, inOrderLeft, inOrderRight) {
if (inOrderLeft > inOrderRight) return null;
// 根据前序遍历的第一个结果,也就是根节点的值,将中序遍历分割成左右子树
const rootIndex = map.get(preOrderIndex[preOrderIndex]);
const left = build(preorder, inorder, preOrderIndex + 1, inOrderLeft, rootIndex - 1);
// eslint-disable-next-line max-len
const right = build(preorder, inorder, preOrderIndex + (rootIndex - inOrderLeft) + 1, rootIndex + 1, inOrderRight);
return new TreeNode(preorder[preOrderIndex], left, right);
}
}
// REVIEW:
/*
利用迭代
*/
function f4(preorder, inorder) {
if (!preorder || preorder.length === 0) return null;
// 根节点是前序遍历的第一个元素
const root = new TreeNode(preorder[0]);
// 栈用于存储节点
const stack = [root];
let inorderIndex = 0;
// 从前序遍历数组的第二个元素开始遍历
for (let i = 1; i < preorder.length; i++) {
const preorderVal = preorder[i];
let node = stack[stack.length - 1];
// 如果栈顶元素的值不等于当前中序遍历的值,说明是左子树的节点
if (node.val !== inorder[inorderIndex]) {
node.left = new TreeNode(preorderVal);
stack.push(node.left);
} else {
// 否则说明当前节点是右子树的节点
while (stack.length > 0 && stack[stack.length - 1].val === inorder[inorderIndex]) {
node = stack.pop(); // 回溯
inorderIndex++;
}
// 创建右子树节点并将其压入栈
node.right = new TreeNode(preorderVal);
stack.push(node.right);
}
}
return root;
}

View File

@ -0,0 +1,65 @@
/**
* 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}
*/
const hasPathSum = function (root, targetSum) {
};
/*
思路利用递归抽象问题如果左节点或者右节点满足 targetSum - root.val那么整个树就满足
*/
function f1(root, targetSum) {
if (!root) return false; // 如果当前节点为空,返回 false
// 如果当前节点是叶子节点,且值等于目标值,返回 true
if (!root.left && !root.right) return root.val === targetSum;
// 递归判断左子树或右子树是否存在满足条件的路径
const remainingSum = targetSum - root.val;
return hasPathSum(root.left, remainingSum) || hasPathSum(root.right, remainingSum);
}
/*
思路利用层序遍历获得从根节点到当前节点的所有和只需准备两个队列一个队列用于层序遍历一个节点用于保存层序遍历到的
每一个值得路径综合如果这个节点是一个叶子节点就检查总和是不是和targetSum一致如果不一致继续检查其他得叶子节点
*/
function f2(root, targetSum) {
if (!root) return false; // 如果根节点为空直接返回false
const nodeQue = [root]; // 层序遍历的队列
const sumQue = [root.val]; // 遍历nodeQue的节点的路径总和和nodeQue是同步的
while (nodeQue.length > 0) {
const node = nodeQue.shift();
const sum = sumQue.shift();
// 如果当前节点为叶子节点检查sum是否和targetSum相等
if (!node.left && !node.right && sum === targetSum) return true;
// 如果左右节点存在将左右节点存入队列对应的路径和存入sum队列
if (node.left) {
nodeQue.push(node.left);
sumQue.push(sum + node.left.val);
}
if (node.right) {
nodeQue.push(node.right);
sumQue.push(sum + node.right.val);
}
}
return false;
}
/*
思路三和f1是一样的只不过利用dfs来代替递归//TODO
*/

View File

@ -0,0 +1,102 @@
/**
* 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 {void} Do not return anything, modify root in-place instead.
*/
import TreeNode from './tool';
const flatten = function (root) {
};
/*
思路直接先序遍历整个二叉树将整个节点按照顺序存进数组最后遍历它们把它们连接起来
*/
function f1(root) {
// 如果为空返回
if (!root) return root;
const preNodes = [];// 储存先序遍历的数组
const dummyHead = new TreeNode(1); // 哑节点,用于构建链表
// 先序遍历
const stack = [root]; // 先序遍历需要用到的栈
while (stack.length) {
const node = stack.pop(); // 取出根节点
preNodes.push(node);
if (node.right) stack.push(node.right);
if (node.left) stack.push(node.left);
}
// 将数组中所有的节点通过right链接到dummyHead
let preNode = dummyHead;
for (let i = 0; i < preNodes.length; i++) {
preNodes[i].left = null; // 左节点滞空
preNode.right = preNodes[i];
preNode = preNodes[i];
}
return dummyHead.right;
}
/*
利用栈来前序遍历思路和上面迭代一致
*/
function f2(root) {
const preorderTraversal = (node, nodeList) => {
if (node) {
nodeList.push(node);
preorderTraversal(node.left, nodeList);
preorderTraversal(node.right, nodeList);
}
};
if (!root) return root;
const list = [];
// 递归遍历二叉树把节点放进list中
preorderTraversal(root, list);
// 将所有节点链接起来
const dummyHead = new TreeNode(-1); // 利用哑节点来处理头节点问题
let cur = dummyHead;
for (let i = 0; i < list.length; i++) {
cur.right = list[i];
cur.left = null;
cur = list[i];
}
return dummyHead.right;
}
/*
在前序遍历的同时设置节点根据前序遍历的顺序会先处理左子树再处理右子树所以只要先把右子树
压入栈中再把左子树压入栈中这样当所有的左子树处理完毕会继续处理右子树
*/
function f3(root) {
if (!root) return root;
const stack = [root];
// 要链接到的节点,初始为空,表示字节的的前面一个节点
let preNode = null;
while (stack.length) {
const node = stack.pop();
// 将当前节点链接到preNode.right
if (preNode) {
preNode.right = node;
preNode.left = null;
}
// 将右子树压入栈中
if (node.right) stack.push(node.right);
// 将左子树压入栈中
if (node.left) stack.push(node.left);
preNode = node;
}
}

View File

@ -0,0 +1,133 @@
/**
* // Definition for a _Node.
* function _Node(val, left, right, next) {
* this.val = val === undefined ? null : val;
* this.left = left === undefined ? null : left;
* this.right = right === undefined ? null : right;
* this.next = next === undefined ? null : next;
* };
*/
/**
* @param {_Node} root
* @return {_Node}
*/
const connect = function (root) {
};
/*
按照题目的意思很容易想到层序遍历利用BFS拿到每一层的所有元素定义一个last变量表示需要
设置next的节点默认为当前层的第一个之后遍历后面的所有元素last.next = node即可node.next
默认为null所以不要考虑为最后一个节点设置next
*/
function f1(root) {
if (!root) return root;
const queue = [root]; // 定义队列,用于层序遍历
while (queue.length) {
// 获取当前层的节点个数用于遍历设置next
let n = queue.length;
// 定义last用于设置next
let last = null;
for (let i = 0; i < n; i++) {
const node = queue.shift();
if (node.left) queue.push(node.left);
if (node.right) queue.push(node.right);
// 如果不是第一个node
if (i !== 0) {
last.next = node;
}
last = node;
}
n = queue.length;
}
return root;
}
/*
利用栈来储存每一层的节点再获取下一层的节点有点浪费空间因为原本就已经有一个next
来指向下一层的节点了我们可以利用这个特点来遍历每一层的节点并且为它下一层的节点
建立next指针直到下一层没有节点这样所有的节点就都构建好next指针
*/
function f2(root) {
if (!root) return root; // 如果根节点为空,直接返回
let nextStart = root; // 初始时为根节点
// 1. 每一层的遍历,直到没有更多节点
while (nextStart) {
let last = null; // 记录每一层的最后一个节点,用来连接
let cur = nextStart; // 当前层的遍历指针
nextStart = null; // 下一层的起始节点初始化为空
// 2. 遍历当前层的节点
while (cur) {
// 3. 如果当前节点有左子节点
if (cur.left) {
// 如果last不为空说明上一个节点已连接需要更新next
if (last) last.next = cur.left;
last = cur.left; // 更新last为当前左子节点
// 如果nextStart为空表示当前左子节点是下一层的第一个节点
if (!nextStart) nextStart = cur.left;
}
// 如果当前节点有右子节点
if (cur.right) {
if (last) last.next = cur.right;
last = cur.right; // 更新last为当前右子节点
// 如果nextStart为空表示当前右子节点是下一层的第一个节点
if (!nextStart) nextStart = cur.right;
}
// 移动到当前节点的下一个节点
cur = cur.next;
}
}
// 返回根节点,确保修改后的树结构被返回
return root;
}
/*
思路和f2一致但是通过递归处理
*/
function f3(node) {
if (!node) return; // 如果节点为空,直接返回
let nextStart = null; // 用来记录下一层的第一个节点
let last = null; // 用来记录当前层最后一个节点
// 遍历当前层的所有节点
while (node) {
// 连接左子节点
if (node.left) {
if (last) {
last.next = node.left; // 连接当前节点的上一个节点的next
}
last = node.left; // 更新last为当前左子节点
if (!nextStart) nextStart = node.left; // 如果nextStart为空设置为当前左子节点
}
// 连接右子节点
if (node.right) {
if (last) {
last.next = node.right; // 连接当前节点的上一个节点的next
}
last = node.right; // 更新last为当前右子节点
if (!nextStart) nextStart = node.right; // 如果nextStart为空设置为当前右子节点
}
node = node.next; // 移动到当前节点的下一个节点
}
// 递归处理下一层
f3(nextStart);
return node;
}

View File

@ -0,0 +1,64 @@
/**
* 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}
* https://leetcode.cn/problems/binary-tree-maximum-path-sum/?envType=study-plan-v2&envId=top-interview-150
*/
const maxPathSum = function (root) {
};
/**
* 计算二叉树中的最大路径和路径和的定义是
* 从任意节点出发沿着父子节点的路径进行移动且路径和的值为路径上所有节点的值之和
* 一个路径的最大和可能穿过根节点也可能不穿过根节点
*
* 1. 初始化变量 `maxSum` 为负无穷表示初始时的最大路径和
* 2. 使用一个递归函数 `maxGain(node)`计算以当前节点为起点的路径和贡献值
* - 如果当前节点为空返回 0没有路径贡献
* - 对于每个节点我们分别计算其左子树和右子树的最大贡献值
* - `leftGain` `rightGain` 代表左右子树的最大路径贡献若为负数则选择为 0避免影响结果
* - 计算该节点的最大路径和 `priceNewPath`这是通过当前节点的值加上左右子树的最大贡献值得到的
* - 更新 `maxSum` 为当前路径和与已知最大路径和的较大者
* - 返回该节点的最大贡献值它等于当前节点的值加上左右子树贡献中的较大者
*
* 3. `maxPathSum` 调用 `maxGain(root)` 来递归计算最大路径和
* 4. 最终`maxSum` 将包含二叉树中的最大路径和返回这个值作为结果
*
* 需要注意
* - 每个节点的最大贡献值是考虑到该节点本身以及它的左右子节点的最大贡献
* - 我们只选择左右子树中贡献值大于零的部分因为负值的路径会减少总和
* - `maxSum` 记录的是整个树中的最大路径和它可能并不经过根节点
*/
function f1(root) {
let maxSum = -Infinity; // 初始化为最小的整数
// 内部的递归函数,返回以当前节点为起点的最大路径贡献值
function maxGain(node) {
if (!node) return 0;
// 递归计算左右子节点的最大贡献值只有在大于0时才会选择该节点
const leftGain = Math.max(maxGain(node.left), 0);
const rightGain = Math.max(maxGain(node.right), 0);
// 当前节点的最大路径和为该节点值加上左右子树的最大贡献值
const priceNewPath = node.val + leftGain + rightGain;
// 更新答案
maxSum = Math.max(maxSum, priceNewPath);
// 返回该节点的最大贡献值
return node.val + Math.max(leftGain, rightGain);
}
maxGain(root); // 开始递归计算
return maxSum; // 返回结果
}

View File

@ -0,0 +1,110 @@
/**
* 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}
*/
const sumNumbers = function (root) {
};
/*
思路利用DFS来遍历每一个节点直到遍历到叶子节点把从root节点到叶子节点代表的数字求出了之后求和处理完所有的叶子节点
那么sum就是我们要返回的最终结果叶子节点要想知道从root节点到父节点所代表的数字就必须用一个numStack的栈储存
*/
function f1(root) {
let sum = 0; // 根节点到叶子节点代表的数字之和
const nodeStack = [root]; // 遍历每一个节点
const numStack = [root.val]; // 每一个节点所表示的数字
// DFS遍历所有节点
while (nodeStack.length > 0) {
// 弹出要处理的节点和root节点到这个节点表示的数字
const node = nodeStack.pop();
const nodeNum = numStack.pop();
// 如果当前节点是叶子节点将他与sum求和
if (!node.left && !node.right) {
sum += nodeNum;
continue;
}
// 如果当前节点的左右子节点存在将它和它表示的num压入栈中后续处理
if (node.right) {
nodeStack.push(node.right);
numStack.push(nodeNum * 10 + node.right.val);
}
if (node.left) {
nodeStack.push(node.left);
numStack.push(nodeNum * 10 + node.left.val);
}
}
// 返回结果
return sum;
}
/*
这个题目利用BFS也能解决只需改一下f1就行把栈改成队列
*/
function f2(root) {
let sum = 0; // 根节点到叶子节点代表的数字之和
const nodeQue = [root]; // 遍历每一个节点
const numQue = [root.val]; // 每一个节点所表示的数字
// DFS遍历所有节点
while (nodeQue.length > 0) {
// 弹出要处理的节点和root节点到这个节点表示的数字
const node = nodeQue.shift();
const nodeNum = numQue.shift();
// 如果当前节点是叶子节点将他与sum求和
if (!node.left && !node.right) {
sum += nodeNum;
continue;
}
// 如果当前节点的左右子节点存在将它和它表示的num压入栈中后续处理
if (node.right) {
nodeQue.push(node.right);
numQue.push(nodeNum * 10 + node.right.val);
}
if (node.left) {
nodeQue.push(node.left);
numQue.push(nodeNum * 10 + node.left.val);
}
}
// 返回结果
return sum;
}
/*
利用dfs递归递归函数接收两个参数一个参数为node表示当前的节点一个参数为prevNum表示根节点到父节点表示的数字返回值
表示的是这个根节点到这个节点下面任意字节的表示的数字的总和
*/
const dfs = (root, prevNum) => {
if (root === null) {
return 0;
}
const sum = prevNum * 10 + root.val;
if (root.left == null && root.right == null) {
return sum;
}
// 将所有叶子节点的值求和
return dfs(root.left, sum) + dfs(root.right, sum);
};
const f3 = function (root) {
return dfs(root, 0);
};
// 上面的递归函数就做了两件事情,找叶子节点,找到,将他与父节点表示的数字组合成新的数字,最后求和

View File

@ -0,0 +1,77 @@
/*
思路根据题意在调用next()的时候我们需要按照二叉搜索树的中序遍历顺序返回结果所以可以先对BST进行中序遍历然后把遍历的结果
储存到this.nodeVals中再维护一个this.idx 来表示next 需要返回值得下标
*/
/**
* 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
*/
const BSTIterator = function (root) {
this.arr = []; // 二叉搜索树中序遍历结果
this.idx = 0; // 调用next返回得值
BSTIterator.inOrderTravers(root, this.arr); // 把中序遍历结果存入this.arr
};
/**
* @return {number}
*/
BSTIterator.prototype.next = function () {
return this.arr[this.idx++];
};
/**
* @return {boolean}
*/
BSTIterator.prototype.hasNext = function () {
// 如果this.idx 小于this.arr.lengthz则表示调用next能获取下一个元素
return this.idx < this.arr.length;
};
BSTIterator.inOrderTravers = function (root, arr) {
if (!root) return;
// 处理左子树
BSTIterator.inOrderTravers(root.left, arr);
// 将值存入数组中
arr.push(root.val);
// 处理右子树
BSTIterator.inOrderTravers(root.right, arr);
};
/**
* Your BSTIterator object will be instantiated and called as such:
* var obj = new BSTIterator(root)
* var param_1 = obj.next()
* var param_2 = obj.hasNext()
*/
/*
利用中序遍历迭代类维护一个栈和调用next应该打印得值
*/
// var BSTIterator = function(root) {
// this.cur = root;
// this.stack = [];
// };
// BSTIterator.prototype.next = function() {
// while (this.cur) {
// this.stack.push(this.cur);
// this.cur = this.cur.left;
// }
// this.cur = this.stack.pop();
// const ret = this.cur.val;
// this.cur = this.cur.right;
// return ret;
// };
// BSTIterator.prototype.hasNext = function() {
// return this.cur !== null || this.stack.length;
// };

View File

@ -0,0 +1,98 @@
/**
* 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}
*/
const countNodes = function (root) {
};
/*
思路以任何方式遍历一遍统计个数
*/
function f1(root) {
let count = 0;
// 中序遍历递归函数
const inTarverse = (node) => {
// 如果root为空直接返回
if (!node) return;
// 调用左子树
inTarverse(node.left);
// 统计当前节点
count++;
// 调用右子树
inTarverse(node.right);
};
inTarverse(root);
return count;
}
/*
除了遍历所有节点统计个数还可以利用完全二叉树个数与位置的关系通过二分查找来判断节点的个数分为两步
第一步首先获得这棵完全二叉树的高度通过高度我们可以判断一颗完全二叉树的节点数量范围[2^n, 2^n - 1]个节点
第二步节点个数的二进制和位置右某种关系[1,2,3]来看这个例子首先根节点也就是第一个节点那么它的二进制就是
1再来看第二个2,二进制位103,位11这还不够明显再加四个数[1,2,3,4,5,6,7],4->100,5->101,6->110,7->111,这个规律就是
二进制位决定了这个节点从根节点到这个位置的路径7->111第一个1表示的是根节点第二个1表示这个节点在根节点的右子树第三个1
表示的是在更节点的右节点的右节点
过程编写一个exists 判断指定位置的节点是否在root中存在,接收三个参数根节点root树的高度指定要判断的节点
*/
/**
*
* @param {*} root 根节点
* @param {*} h 完全二叉树的高度
* @param {*} k 最后一层的某一个节点 [2^n, 2^n - 1]
*/
const exists = (root, h, k) => {
// 初始bits位 1 << (h-1)假设h是3,那么默认值位100如果我们要查找第12个数是否存在就可以通过 1100 & 0100来
// 判断是在右子树还是在左子树
let bits = 1 << (h - 1);
let node = root;
while (bits) {
if (bits & k) { // bits&k不等于0表示这个节点应该往右子树查找
node = root.right;
} else { // 反之在左子树
node = root.left;
}
bits >>= 1;
}
// 判断node是否等于空
return node === null;
};
function f2(root) {
if (!root) return 0;
// 查找树的深度
let h = 0;
let node = root.left;
while (node) {
h++;
node = node.left;
}
// 通过树的深度计数出数量的范围 low和high
let low = 2 ** h;
let high = 2 ** (h + 1) - 1;
// 二分查找知道low===high结束查找, 以h=3来思考[815]
while (low < high) {
// 计数出中间位置
const mid = low + Math.floor((high - low) / 2);
if (exists(root, h, mid)) {
// 如果这个树存在那么节点个数大于等于这个数将左边界移动到mid
low = mid;
} else {
high = mid - 1;
}
}
// 此时low和high会相等这个位置就是二叉树的个数
return low;
}

View File

@ -0,0 +1,84 @@
/**
* 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)
* }
*/
/**
* https://leetcode.cn/problems/invert-binary-tree/?envType=study-plan-v2&envId=top-interview-150
* @param {TreeNode} root
* @return {TreeNode}
*/
const invertTree = function (root) {
};
/*
利用递归来抽象问题交换一颗树的左右子树不就可以做到翻转了函数f1只做单一的功能就是反转
一颗二叉树递归调用就可以翻转从根节点开始的整棵树
*/
function f1(root) {
if (!root) return; // 如果当前节点为空,直接返回
const left = root.left;
const right = root.right;
// 交换左右两棵树的位置
root.left = right;
root.right = left;
// 递归调用
f1(root.left);
f1(root.right);
}
/*
使用bfs来翻转一颗树
*/
function f2(root) {
if (!root) return root;
const queue = [root]; // bfs所需要的队列
while (queue.length > 0) {
// 从队列中取出节点实际顺序为从上到下从左到右BFS)
const node = queue.shift();
// 交换左右两个节点
const tmp = node.left;
node.left = node.right;
node.right = tmp;
// 如果左子节点存在,加入队列
if (node.left) queue.push(node.left);
// 如果右子节点存在,加入队列
if (node.right) queue.push(node.right);
}
return root;
}
/*
使用DFS来翻转一颗树
*/
function f3(root) {
if (!root) return root;
const stack = [root]; // dfs所需要的栈
while (stack.length > 0) {
// 从栈中取出节点DFS
const node = stack.pop();
// 交换左右两棵子树
const tmp = node.left;
node.left = node.right;
node.right = tmp;
// 如果字节的不为空压入栈继续dfs,
if (node.left) stack.push(node.left);
if (node.right) stack.push(node.right);
}
return root;
}

View File

@ -0,0 +1,90 @@
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root
* @param {TreeNode} p
* @param {TreeNode} q
* @return {TreeNode}
* https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/?envType=study-plan-v2&envId=top-interview-150
*/
const lowestCommonAncestor = function (root, p, q) {
};
/*
利用dfs来遍历每一节点根据定义如果节点x是p和q的最近的公共祖先无外乎就下面几种情况
1. p在x的左子树q在x的右子树反之一样 lson && rson
2. p就是x那么q一定存在 lson rson, (x===p||x===q) && (lson || rson)
在遍历的时候只要满足上面条件的节点就是要寻找的最近公共祖先
*/
function f1(root, p, q) {
let ans;
/*
dfs遍历只干一件事就是判断root表示的这个数是否有p或者q
*/
const dfs = (root, p, q) => {
// 如果当前节点为空返回false
if (!root) return false;
// 查找左右子树是否包含q或q
const lson = dfs(root.left, p, q);
const rson = dfs(root.right, p, q);
// 判断当前节点是否是最近公共祖先
if ((lson && rson) || ((root === p || root === q) && (lson || rson))) {
ans = root;
}
// 返回函数判断
return lson || rson || root === p || root === q;
};
dfs(root, p, q);
return ans;
}
/*
遍历所有的节点给他们设置对应的父节点之后不停的回溯q的父节点并且把它们存入到一个set中之后再回溯p节点的父节点如果p节点的父节点
在set中那么这个节点就是最近公共祖先
*/
function f2(root, p, q) {
const parentMap = new Map(); // 储存父节点映射
const visited = new Set(); // 记录已访问的节点
// 深度优先搜索DFS来构建父节点映射
const dfs = (node) => {
if (!node) return;
if (node.left) {
parentMap.set(node.left, node); // 记录左节点的父节点
dfs(node.left); // 继续递归
}
if (node.right) {
parentMap.set(node.right, node); // 记录右节点的父节点
dfs(node.right); // 继续递归
}
};
// 执行DFS来构建父节点映射
dfs(root);
// 追溯p的路径并标记
while (p) {
visited.add(p); // 标记p节点
p = parentMap.get(p); // 跳到p的父节点
}
// 追溯q的路径并查找是否有交点
while (q) {
if (visited.has(q)) return q; // 找到交点返回q
q = parentMap.get(q); // 跳到q的父节点
}
return null; // 如果没有交点返回null
}

View File

@ -0,0 +1,5 @@
export default function TreeNode(val, left, right) {
this.val = (val === undefined ? 0 : val);
this.left = (left === undefined ? null : left);
this.right = (right === undefined ? null : right);
}