目录
声明:从此处开始使用js刷题了 ̄へ ̄,之后有空会将之前用c++写的题重写一遍
二叉树的定义(链式存储)
function TreeNode(val, left, right) {
this.val = (val===undefined ? 0 : val)
this.left = (left===undefined ? null : left)
this.right = (right===undefined ? null : right)
}
144. 二叉树的前序遍历
二叉树的遍历:
- 迭代(运用栈)
- 递归(易理解)
递归步骤:
-
确定递归函数的参数和返回值: 确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数, 并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型。
-
确定终止条件: 写完了递归算法, 运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出。
-
确定单层递归的逻辑: 确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。
前序遍历:
var preorderTraversal = function(root) {
let res = [];
const dfs = function(node) {
if (!node) return null;
res.push(node.val);
dfs(node.left);
dfs(node.right);
}
// 只使用一个参数 使用闭包进行存储结果
dfs(root);
return res;
};
掌握思路后,中序和后序遍历也就很容易了
迭代:
递归的实现就是:每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中
因此我们也可以使用栈进行遍历
前序遍历:先将根节点放入栈中,再将右节点入栈,最后是左节点,这样出栈时的顺序才能是中左右的顺序
var preorderTraversal = function(root) {
let res = [];
if (!root) return res;
const st = [root];
while (st.length) {
let cur = st.pop();
res.push(cur.val);
cur.right && st.push(cur.right);
cur.left && st.push(cur.left);
}
return res;
};
中序遍历:这里前序遍历方法不能通用,由于前序处理和访问的元素都是中间节点,处理顺序和访问顺序是一致的,而中序需要先到达数的左下方,再开始处理,处理顺序和访问顺序不同
因此我们采取的办法是:
- 指针遍历访问节点
- 栈处理节点的元素
var inorderTraversal = function(root) {
let res = [];
if (!root) return res;
let cur = root, st = [];
while (cur || st.length) {
if (cur) {
st.push(cur);
cur = cur.left;
} else {
cur = st.pop();
res.push(cur.val);
cur = cur.right;
}
}
return res;
};
后序遍历:这里可以和前序遍历做个对比,前序遍历的顺序是中左右,那么我们将顺序调整为中右左,然后反转res,输出的结果就变成左右中了
var postorderTraversal = function(root) {
let res = [];
if (!root) return res;
let st = [root];
while (st.length) {
let cur = st.pop();
res.push(cur.val);
cur.left && st.push(cur.left);
cur.right && st.push(cur.right);
}
return res.reverse();
};
层序遍历:
层序遍历和上文的三种遍历的实现不同,需要借助队列,实际上就是图的BFS(广度优先遍历)
var levelOrder = function(root) {
let que = [], res = [];
if (root === null) return res;
que.push(root);
while (que.length) {
let cur = [], n = que.length;
for (let i = 0;i < n; i++) {
let node = que.shift();
cur.push(node.val);
node.left && que.push(node.left);
node.right && que.push(node.right);
}
res.push(cur);
}
return res;
};
226. 翻转二叉树
- 比较简单,但是可以思考一下自己的遍历顺序
- 这里使用的前序遍历,先交换,然后反转左子树,反转右子树
var invertTree = function(root) {
if (root === null) return null;
let t = root.left;
root.left = root.right;
root.right = t;
invertTree(root.left);
invertTree(root.right);
return root;
};
101. 对称二叉树
- 比较二叉树外侧元素和内侧元素来判断对称,分别以根节点的左右节点为子树根进行伪后序遍历(一个数遍历顺序为:左右中, 一个数遍历顺序为:右左中)
- 递归法(代码简洁):
两节点有一个为null,一个不为null,或值不等时,返回false
两节点都为null或两节点的值相等时,返回true
var isSymmetric = function(root) {
//使用递归遍历左右子树 递归三部曲
// 1. 确定递归的参数 root.left root.right和返回值true false
const compareNode = (left, right) => {
//2. 确定终止条件 空的情况
if (left !== null && right === null || left === null && right !== null) {
return false;
} else if (left === null && right === null) {
return true;
} else if (left.val !== right.val) {
return false;
}
//3. 确定单层递归逻辑
let outSide = compareNode(left.left, right.right);
let inSide = compareNode(left.right, right.left);
return outSide && inSide;
}
return compareNode(root.left, root.right);
};
- 迭代法
将左右子树要比较的元素放入栈中,然后成对取出比较(个人认为更好理解)
var isSymmetric = function(root) {
if (!root) return true;
let st = [];
st.push(root.left);
st.push(root.right);
while (st.length) {
let right = st.pop();
let left = st.pop();
if (!left && !right) { // 左右都为null
continue;
} else if (!left || !right || left.val !== right.val) { // 左或右一个为null 或 值不等
return false;
}
st.push(left.left);
st.push(right.right);
st.push(left.right);
st.push(right.left);
}
return true;
};
572. 另一棵树的子树
- 和对称二叉树用的一个方法,但是写的时候思路太乱了,这里记录一下
- 最开始的想法是,遍历root的各个节点和subroot相比较,最后代码写的非常臃肿
var isSubtree = function(root, subRoot) {
const compareNode = (left, right) => {
if (!left && !right) return true;
else if (!left || !right || left.val !== right.val) return false;
return compareNode(left.left, right.left) && compareNode(left.right, right.right);
}
const findChild = (root) => {
if (!root) return;
nodes.push(root);
findChild(root.left);
findChild(root.right);
}
let nodes = [];
findChild(root);
for (let i = 0; i < nodes.length; i++) {
if (compareNode(nodes[i], subRoot)) return true;
}
return false;
};
- 正解(特别关注一下dfs里面的if判断)
var isSubtree = function(root, subRoot) {
const compareNode = (left, right) => {
if (!left && !right) return true;
else if (!left || !right || left.val !== right.val) return false;
return compareNode(left.left, right.left) && compareNode(left.right, right.right);
}
const dfs = (root, subRoot) => {
if (!root) return false; // 若root为null,将取不到左右节点,会报错
return compareNode(root, subRoot) || dfs(root.left, subRoot) || dfs(root.right, subRoot);
}
return dfs(root, subRoot);
};
104. 二叉树的最大深度
- 两种方法(迭代、递归)
- 迭代使用层序遍历(也是最初的思路)
var maxDepth = function(root) {
let l = 0;
if (!root) return l;
let que = [root];
while (que.length) {
let n = que.length;
for (let i = 0; i < n; i++) {
let node = que.shift();
node.left && que.push(node.left);
node.right && que.push(node.right);
}
// 每遍历一层就给深度加1
l++;
}
return l;
};
- 递归法
本题可以使用前序(中左右),也可以使用后序遍历(左右中),使用前序求的就是深度,使用后序求的是高度。
- 二叉树节点的深度:指从根节点到该节点的最长简单路径边的条数或者节点数(取决于深度从0开始还是从1开始)
- 二叉树节点的高度:指从该节点到叶子节点的最长简单路径边的条数后者节点数(取决于高度从0开始还是从1开始)
- 深度--根节点到节点, 高度--节点到叶子节点
而根节点的高度就是二叉树的最大深度,所以本题中我们通过后序求的根节点高度来求的二叉树最大深度。
var maxdepth = function(root) {
//使用递归的方法 递归三部曲
//1. 确定递归函数的参数和返回值
const getdepth=function(node){
//2. 确定终止条件
if(node===null){
return 0;
}
//3. 确定单层逻辑
let leftdepth=getdepth(node.left);
let rightdepth=getdepth(node.right);
let depth=1+Math.max(leftdepth,rightdepth);
return depth;
}
return getdepth(root);
};
111. 二叉树的最小深度
- 和前一题有不同之处
- 迭代不解释
var minDepth = function(root) {
if (root === null) return 0;
let que = [root], res = 0;
while (que.length) {
let n = que.length;
res++;
for (let i = 0; i < n; i++) {
let node = que.shift();
if (!node.left && !node.right) return res;
node.left && que.push(node.left);
node.right && que.push(node.right);
}
}
return res;
};
- 递归的话,需要注意单层递归的逻辑
- 第一次写就写错了(这样没有左孩子的分支会是最短深度)
let depth = 1 + Math.min(node.left, node.right);
- 求最小深度和最大深度的差别主要在于处理左右孩子不为空的逻辑
var minDepth = function(root) {
const getDepth = (node) => {
if (!node) return 0;
let leftDepth = getDepth(node.left); // 左
let rightDepth = getDepth(node.right); // 右
// 左子树为空,右不为空
if (!node.left && node.right) return 1 + rightDepth; // 中
// 右子树为空,左不为空
if (!node.right && node.left) return 1 + leftDepth;
let res = 1 + Math.min(leftDepth, rightDepth);
return res;
}
return getDepth(root);
};
222. 完全二叉树的节点个数
- 层序遍历不解释
var countNodes = function(root) {
if (!root) return 0;
let que = [root], res = 1;
while (que.length) {
let n = que.length;
for (let i = 0; i < n; i++) {
let node = que.shift();
if (node.left) {
que.push(node.left);
res++;
}
if (node.right) {
que.push(node.right);
res++;
}
}
}
return res;
};
- 递归(后序遍历)
var countNodes = function(root) {
const getNumber = (node) => {
if (!node) return 0;
let leftNumber = getNumber(node.left);
let rightNumber = getNumber(node.right);
return 1 + leftNumber + rightNumber;
}
return getNumber(root);
};
- 上面两种方法利用的是普通二叉树解决,下面介绍一下完全二叉树(另一种视角的递归)
var countNodes = function(root) {
if (!root) return 0;
let left = root.left, right = root.right;
let leftDepth = 1, rightDepth = 1; // 左右子树的深度
while (left) {
left = left.left;
leftDepth++;
}
while (right) {
right = right.right;
rightDepth++;
}
// 相等,说明该子树为满二叉树
if (leftDepth === rightDepth) return Math.pow(2, leftDepth) - 1;
// 不相等继续递归
else return 1 + countNodes(root.left) + countNodes(root.right);
};