From e7559ec1c76a5352a1de9c5e500af25e1af95b31 Mon Sep 17 00:00:00 2001 From: LouisFonda Date: Fri, 25 Apr 2025 14:41:15 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BA=8C=E5=8F=89=E6=A0=91=E7=9B=B8?= =?UTF-8?q?=E5=85=B3=E9=A2=98=E7=9B=AE100,101,104,105,112,114,117,124,129,?= =?UTF-8?q?173,222,226,236?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../binary-tree/100相同的数.js | 74 +++++++++ .../binary-tree/101对称二叉树.js | 101 ++++++++++++ .../binary-tree/104二叉树的最大深度.js | 49 ++++++ .../105从前序与中序遍历序列构造二叉树.js | 148 ++++++++++++++++++ .../binary-tree/112路径总和.js | 65 ++++++++ .../binary-tree/114二叉树展开为列表.js | 102 ++++++++++++ .../117填充每个节点的下一个右侧节点指针 II.js | 133 ++++++++++++++++ .../binary-tree/124二叉树中最大路径和.js | 64 ++++++++ .../129求根节点到叶子节点数字之和.js | 110 +++++++++++++ .../binary-tree/173二叉搜索树迭代器.js | 77 +++++++++ .../binary-tree/222完全二叉树得节点个数.js | 98 ++++++++++++ .../binary-tree/226翻转二叉树.js | 84 ++++++++++ .../binary-tree/236二叉树的最近公共祖先.js | 90 +++++++++++ top-interview-leetcode150/binary-tree/tool.js | 5 + 14 files changed, 1200 insertions(+) create mode 100644 top-interview-leetcode150/binary-tree/100相同的数.js create mode 100644 top-interview-leetcode150/binary-tree/101对称二叉树.js create mode 100644 top-interview-leetcode150/binary-tree/104二叉树的最大深度.js create mode 100644 top-interview-leetcode150/binary-tree/105从前序与中序遍历序列构造二叉树.js create mode 100644 top-interview-leetcode150/binary-tree/112路径总和.js create mode 100644 top-interview-leetcode150/binary-tree/114二叉树展开为列表.js create mode 100644 top-interview-leetcode150/binary-tree/117填充每个节点的下一个右侧节点指针 II.js create mode 100644 top-interview-leetcode150/binary-tree/124二叉树中最大路径和.js create mode 100644 top-interview-leetcode150/binary-tree/129求根节点到叶子节点数字之和.js create mode 100644 top-interview-leetcode150/binary-tree/173二叉搜索树迭代器.js create mode 100644 top-interview-leetcode150/binary-tree/222完全二叉树得节点个数.js create mode 100644 top-interview-leetcode150/binary-tree/226翻转二叉树.js create mode 100644 top-interview-leetcode150/binary-tree/236二叉树的最近公共祖先.js create mode 100644 top-interview-leetcode150/binary-tree/tool.js diff --git a/top-interview-leetcode150/binary-tree/100相同的数.js b/top-interview-leetcode150/binary-tree/100相同的数.js new file mode 100644 index 0000000..d669362 --- /dev/null +++ b/top-interview-leetcode150/binary-tree/100相同的数.js @@ -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; +} diff --git a/top-interview-leetcode150/binary-tree/101对称二叉树.js b/top-interview-leetcode150/binary-tree/101对称二叉树.js new file mode 100644 index 0000000..195459d --- /dev/null +++ b/top-interview-leetcode150/binary-tree/101对称二叉树.js @@ -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; +} diff --git a/top-interview-leetcode150/binary-tree/104二叉树的最大深度.js b/top-interview-leetcode150/binary-tree/104二叉树的最大深度.js new file mode 100644 index 0000000..eb86b39 --- /dev/null +++ b/top-interview-leetcode150/binary-tree/104二叉树的最大深度.js @@ -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; +} diff --git a/top-interview-leetcode150/binary-tree/105从前序与中序遍历序列构造二叉树.js b/top-interview-leetcode150/binary-tree/105从前序与中序遍历序列构造二叉树.js new file mode 100644 index 0000000..16ca7f5 --- /dev/null +++ b/top-interview-leetcode150/binary-tree/105从前序与中序遍历序列构造二叉树.js @@ -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; +} diff --git a/top-interview-leetcode150/binary-tree/112路径总和.js b/top-interview-leetcode150/binary-tree/112路径总和.js new file mode 100644 index 0000000..e9fe14c --- /dev/null +++ b/top-interview-leetcode150/binary-tree/112路径总和.js @@ -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 +*/ diff --git a/top-interview-leetcode150/binary-tree/114二叉树展开为列表.js b/top-interview-leetcode150/binary-tree/114二叉树展开为列表.js new file mode 100644 index 0000000..866c683 --- /dev/null +++ b/top-interview-leetcode150/binary-tree/114二叉树展开为列表.js @@ -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; + } +} diff --git a/top-interview-leetcode150/binary-tree/117填充每个节点的下一个右侧节点指针 II.js b/top-interview-leetcode150/binary-tree/117填充每个节点的下一个右侧节点指针 II.js new file mode 100644 index 0000000..ca37b2d --- /dev/null +++ b/top-interview-leetcode150/binary-tree/117填充每个节点的下一个右侧节点指针 II.js @@ -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; +} diff --git a/top-interview-leetcode150/binary-tree/124二叉树中最大路径和.js b/top-interview-leetcode150/binary-tree/124二叉树中最大路径和.js new file mode 100644 index 0000000..7709ef1 --- /dev/null +++ b/top-interview-leetcode150/binary-tree/124二叉树中最大路径和.js @@ -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; // 返回结果 +} diff --git a/top-interview-leetcode150/binary-tree/129求根节点到叶子节点数字之和.js b/top-interview-leetcode150/binary-tree/129求根节点到叶子节点数字之和.js new file mode 100644 index 0000000..58bbadf --- /dev/null +++ b/top-interview-leetcode150/binary-tree/129求根节点到叶子节点数字之和.js @@ -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); +}; +// 上面的递归函数就做了两件事情,找叶子节点,找到,将他与父节点表示的数字组合成新的数字,最后求和 diff --git a/top-interview-leetcode150/binary-tree/173二叉搜索树迭代器.js b/top-interview-leetcode150/binary-tree/173二叉搜索树迭代器.js new file mode 100644 index 0000000..ddb5e06 --- /dev/null +++ b/top-interview-leetcode150/binary-tree/173二叉搜索树迭代器.js @@ -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; +// }; diff --git a/top-interview-leetcode150/binary-tree/222完全二叉树得节点个数.js b/top-interview-leetcode150/binary-tree/222完全二叉树得节点个数.js new file mode 100644 index 0000000..a73b79f --- /dev/null +++ b/top-interview-leetcode150/binary-tree/222完全二叉树得节点个数.js @@ -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,二进制位10,3,位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来思考[8,15] + 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; +} diff --git a/top-interview-leetcode150/binary-tree/226翻转二叉树.js b/top-interview-leetcode150/binary-tree/226翻转二叉树.js new file mode 100644 index 0000000..1d5c707 --- /dev/null +++ b/top-interview-leetcode150/binary-tree/226翻转二叉树.js @@ -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; +} diff --git a/top-interview-leetcode150/binary-tree/236二叉树的最近公共祖先.js b/top-interview-leetcode150/binary-tree/236二叉树的最近公共祖先.js new file mode 100644 index 0000000..7f5c804 --- /dev/null +++ b/top-interview-leetcode150/binary-tree/236二叉树的最近公共祖先.js @@ -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 +} diff --git a/top-interview-leetcode150/binary-tree/tool.js b/top-interview-leetcode150/binary-tree/tool.js new file mode 100644 index 0000000..56f4804 --- /dev/null +++ b/top-interview-leetcode150/binary-tree/tool.js @@ -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); +}