文章目录
二叉树
二叉树是树结构中一种典型的树状结构,每个节点最多只能有两个子节点,一个是左侧子节点,一个是右侧子节点。
二叉树中又有一种比较典型的结构,称为二叉搜索树(BST),它允许左侧节点存储的值比父节点小,右侧存储的值比父节点大(或相等)。
常见题型
二叉树的中序遍历
给定一个二叉树,返回它的中序遍历
//递归实现
var inorderTraversal = function(root, array=[]){
if(root){
inorderTraversal(root.left,array);
array.push(root.val);
inorderTraversal(root.right,array);
}
return array;
};
//非递归实现
var inorderTraversal = function(root){
let array = [];
let stack = [];
let cur = root;
while(cur || stack.length > 0){
while(cur){
stack.push(cur);
cur = cur.left;
}
cur = stack.pop();
array.push(cur.val);
cur = cur.right;
}
return array;
}
前序遍历
给定一个二叉树,返回前序遍历
//递归实现
var preorderTraversal = function(root, array=[]){
if(root){
array.push(root.val);
inorderTraversal(root.left);
inorderTraversal(root.right);
}
return array;
};
//非递归实现
var preorderTraversal = function(root){
let array = [];
let stack = [];
let cur = root;
while(cur || stack.length > 0){
while(cur){
array.push(cur.val);
stack.push(cur);
cur = cur.left;
}
cur = stack.pop();
cur = cur.right;
}
return array;
}
后序遍历
给定一个二叉树,返回它的后序遍历
//递归实现
var postorderTraversal = function(root, array=[]){
if(root){
inorderTraversal(root.left);
inorderTraversal(root.right);
array.push(root.val);
}
return array;
};
//非递归实现
//1.从根节点开始遍历,找左侧子节点并压入栈中
//2.从栈底元素(即根节点)开始找到右侧子节点,若栈顶节点的右节点为空或右节点被访问过,则节点出栈,并访问它,并将节点标记为已访问
//3.若栈顶节点
var postorderTraversal = function(root){
let arr = [];
let stack = [];
let last = null; //标记上一个访问的节点
let cur = root;
while(cur || stack.length > 0){
while(cur){
stack.push(cur);
cur = cur.left;
}
//找到根节点
cur = stack[stack.length - 1];
if(!cur.right || cur.right == last){
cur = stack.pop();
arr.push(cur.val);
last = cur;
cur = null;
}else{
cur = cur.right;
}
}
return arr;
}
重建二叉树
输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
例如输入前序遍历序列
[3,9,20,15,7]
和中序遍历序列[9,3,15,20,7]
,则重建二叉树并返回。
根据前面的遍历情况:
前序遍历:根节点 + 左侧节点的前序遍历 + 右侧节点的前序遍历,依次递归
中序遍历:左侧节点中序遍历 + 根节点 + 右侧节点的中序遍历, 依次类推
后序遍历:左侧节点后序遍历 + 右侧节点后序遍历 + 根节点,依次类推
因此根据以上规律:
- 前序遍历第一个便是根节点 root
- 找到中序遍历中根节点root的位置,根节点左侧为左子树,右侧为右子树
- 截取左子树的中序遍历、右子树的中序遍历
- 截取左子树的前序遍历,右子树的前序遍历
- 根据上述规则重建二叉树
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
//pre和vin分别为前序遍历和中序遍历的数组
var buildTree = function(pre, vin) {
if(pre.length === 0){return null;}
if(pre.length === 1){return new TreeNode(pre[0]);}
let value = pre[0];
let index = vin.indexOf(value);
//根据根节点分隔中序遍历的左右子树
let vinLeft = vin.slice(0,index);
let vinRight = vin.slice(index+1);
//根据中序遍历结果分隔前序遍历的左右子树
let preLeft = pre.slice(1,index+1);
let preRight = pre.slice(idnex+1);
let node = new TreeNode(value);
node.left = buildTree(preLeft, vinLeft);
node.right = buildTree(preRight, vinRight);
return node;
};
对称的二叉树
请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。
如 二叉树 [1,2,2,3,4,4,3] 是对称的。
判断是否为二叉树,需要满足:
- 二叉树的两个根节点相等
- 左子树的右节点和右子树的左节点相同
- 右子树的左节点和左子树的右节点相同
递归所有节点满足以上条件即为对称二叉树
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root
* @return {boolean}
*/
var isSymmetric = function(root) {
return isSymmetricalTree(root, root)
};
function isSymmetricalTree(node1,node2){
if(!node1 && !node2){
return true;
}
if(!node1 || !node2){
return false;
}
if(node1.val != node2.val){
return false;
}
return isSymmetricalTree(node1.left, node2.right) && isSymmetricalTree(node1.right, node2.left)
}
二叉树的镜像
输入一个二叉树,输出它的镜像
思路:递归交换所有节点左右节点的位置
var mirrorTree = function(root) {
if(root){
[root.right, root.left] = [root.left, root.right];
mirrorTree(root.right);
mirrorTree(root.left);
}
return root;
};
二叉搜索树中第k小的元素
给定一个二叉搜索树,找出其中第k个最小的元素
说明:假设k总为 1<= k <= 二叉搜索树元素个数
思路:二叉搜索树的中序遍历即为排序后的节点,先进行中序遍历,之后得到的元素数组中即可找到第k个最小的元素
/**
* 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} k
* @return {number}
*/
var kthSmallest = function(root, k) {
let array = [];
middleThrough(root,array);
return array[k-1];
};
//进行中序遍历
function middleThrough(root,array=[]){
if(root){
middleThrough(root.left,array);
array.push(root.val);
middleThrough(root.right,array);
}
return array;
}
//非递归实现
var kthSmallest = function(root, k) {
const array = [];
const stack = [];
let cur = root;
while(cur || stack.length > 0){
while(cur){
stack.push(cur);
cur = cur.left;
}
cur = stack.pop();
//只需要前k个元素即可
if(array.length<k){
array.push(cur.val);
}else{
break;
}
cur = cur.right;
}
reutrn array[k-1];
}
二叉搜索树的后序遍历序列
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回
true
,否则返回false
。假设输入的数组的任意两个数字都互不相同。若输入[1,6,3,2,5],则返回false
若输入[1,3,2,6,5],则返回true
思路:
后序遍历分为三个部分:最后一个节点为根节点,第二部分为左子树的值都比根节点小,第三部分为右子树的值比根节点大
- 先检测左子树,左侧比根节点小的值都为左子树的值
- 除最后一个节点外和左子树外的其他值为右子树,右子树中只要存在一个值比根节点小,则返回false
- 若存在左右子树,则递归检测左右子树是否符合条件
var verifyPostorder = function(postorder) {
if(postorder.length===0){return true;} //数组长度为0返回true
if(postorder && postorder.length > 0){
let root = postorder[postorder.length-1];
//i的值即为区分左右子树的分界点
for(var i = 0;i < postorder.length-1;i++){
if(postorder[i] > root){
break;
}
}
//i以后除了最后一个节点即为右子树
for(var j = i;j < postorder.length-1;j++){
if(postorder[j] < root){
return false;
}
}
let left = true;
if(i>0){
left = verifyPostorder(postorder.slice(0,i));
}
let right = true;
if(i<postorder.length-1){
right = verifyPostorder(postorder.slice(i,postorder.length-1));
}
return left && right;
}
};
二叉树的最大深度
给定一个二叉树,求其最大深度。
二叉树的深度为根节点到最远子节点的最长路径上的节点数
思路:利用深度优先遍历,一棵二叉树的最大深度等于左子树的最大深度和右子树的最大深度的最大值+1
/** Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
//递归写法
var maxDepth = function(root) {
return !root ? 0 : Math.max(maxDepth(root.left),maxDepth(root.right))+1;
};
//迭代写法
//BFS写法,每一层都遍历完所有节点后,n+1,然后再去遍历下一层节点
var maxDepth = function(root){
if(!root){return 0;}
let quene = [root],n = 0;
while(quene.length){
let arr = [];
while(quene.length){
let cur = quene.shift();
if(cur.left){arr.push(cur.left);}
if(cur.right){arr.push(cur.right);}
}
n++;
quene = arr;
}
return n;
};
二叉树的最小深度
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
思路:
- 若左右子树都不为空,则左子树最小深度和右子树最小深度的最小值+1
- 若左子树为空,则右子树的最小深度+1
- 若右子树为空,则左子树的最小深度+1
var minDepth = function(root) {
if(!root){return 0;}
if(!root.left){return 1 + minDepth(root.right);}
if(!root.right){return 1 + minDepth(root.left);}
return Math.min(minDepth(root.left),minDepth(root.right)) + 1;
}
//广度优先搜索+迭代
var minDepth = function(root){
if(!root){return 0;}
let quene = [root], n = 0;
while(quene.length){
n++;
let len = quene.length;
while(len--){
let node = quene.shift();
if(!node.left && !node.right){reutrn n;}
if(node.left){quene.push(node.left);}
if(node.right){quene.push(node.right);}
}
}
};
平衡二叉树
给定一个二叉树,判断它是否是高度平衡的二叉树。
一棵高度平衡二叉树定义为:每个节点的左右两个子树的高度差的绝对值不超过1。
思路:
- 后序遍历二叉树
- 在遍历二叉树每个节点前都会遍历其左右子树
- 比较左右子树的深度,若差值大于1,则当前子树不平衡,返回false
- 左右子树中有一个是不平衡的,则该二叉树就不平衡
- 若左右子树平衡,返回true
var isBalanced = function(root) {
return balanced(root) !== -1;
};
function balanced(node){
if(!node){
return 0;
}
let left = balanced(node.left);
let right = balanced(node.right);
if(left === -1 || right === -1 || Math.abs(left-right) > 1){
return -1;
}
return Math.max(left, right) + 1;
}
二叉树中和为某一值的路径
输入一棵二叉树和一个整数,打印出二叉树中节点值的和为输入整数的所有路径。从树的根节点开始往下一直到叶节点所经过的节点形成一条路径。
示例:给定如下二叉树,以及目标和 sum = 22,
5
/ \
4 8
/ / \
11 13 4
/ \ / \
7 2 5 1
返回:[[5,4,11,2],[5,8,4,5]]
思路:设定一个结果数组result来存储所有符合条件的路径;用一个栈stack来存储当前路径中的节点;设定一个target来标识当前路径值的和
- 从根节点开始深度优先遍历,每经过一个节点,将节点入栈
- 到达叶子节点,且当前路径之和等于给定目标值,则将其加入结果数组
- 遍历到二叉树的某个节点时可能有2个符合的路径,选择左子树或右子树
- 若存在左子树,则继续向左子树递归
- 若存在右子树,则继续向右子树递归
- 若上述条件不符合,或已经遍历过这一条路径,将当前节点出栈,向上回溯
var pathSum = function(root, sum) {
const result = [];
if(root){
findPath(root,sum,[],0,result);
}
return result;
};
function findPath(node,sum,stack,target,result){
stack.push(node.val);
target += node.val;
if(!node.left && !node.right && target === sum){
result.push(stack.slice(0));
}
if(node.left){
findPath(node.left,sum,stack,target,result);
}
if(node.right){
findPath(node.right,sum,stack,target,result);
}
stack.pop();
}
二叉搜索树与双向链表
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。
思路:二叉搜索树的中序遍历是按照节点值大小排序的,因此需要求出中序遍历
- 递归左子树,找到左子树的最后一个节点,即为排序链表的头节点
- 当前节点变为已经完成转换的链表的最后一个节点
- 递归右子树,找到当前树的最后一个节点
- 回溯到上一层,进行链接
var treeToDoublyList = function(root) {
if(!root){return null;}
let head = null;
let pre = head;
inorder(root);
//完成中序遍历后,pre指向最后一个节点,将其闭合成环装结构
head.left = pre;
pre.right = head;
return head;
function inorder(node,last){
if(!node){return;}
//遍历左子树
inorder(node.left,pre);
if(!pre)
{
//遍历到最左边节点,此时节点就是双向链表的head
head = node;
}else{
pre.right = node;
}
node.left = pre;
pre = node;
//遍历右子树
inorder(node.right,pre);
}
};
树的子结构
输入两棵二叉树A和B,判断B是不是A的子结构(约定空树不是任意一个树的子结构)
若B是A的子结构,则A中有出现和B相同的结构和节点值
思路:
- 首先找到A树和B树根节点相同的节点
- 从此节点开始,递归A、B树比较是否有不同节点
var isSubStructure = function(A, B) {
let result = false;
if(A && B){
if(A.val === B.val){
result = compare(A,B);
}
if(!result){
result = isSubStructure(A.right,B);
}
if(!result){
result = isSubStructure(A.left,B);
}
}
return result;
};
function compare(node1,node2){
if(!node2){return true;}
if(!node1){return false;}
if(node1.val !== node2.val){return false;}
return compare(node1.right,node2.right) && compare(node1.left,node2.left);
}