1.对称二叉树
给定一个二叉树,检查它是否是镜像对称的。
例如,二叉树 [1,2,2,3,4,4,3] 是对称的。
1
/ \
2 2
/ \ / \
3 4 4 3
但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的:
1
/ \
2 2
\ \
3 3
进阶:
你可以运用递归和迭代两种方法解决这个问题吗?
解答:
一棵二叉树对称,则需要满足:根的左右子树是镜像对称的
也就是说,每棵树的左子树都和另外一颗树的右子树镜像对称,左子树的根节点值与右子树的根节点值相等
所以,我们需要比较:
左右子树的根节点值是否相等
左右子树是否镜像对称
边界条件:
左右子树都为 null 时,返回 true
左右子树有一个 null 时,返回 false
解法一:递归(深度优先遍历)
var isSymmetric = function(root) {
if(!root) return true
var isEqual = function(left, right) {
if(!left && !right) return true
if(!left || !right) return false
return left.val === right.val
&& isEqual(left.left, right.right)
&& isEqual(left.right, right.left)
}
return isEqual(root.left, root.right)
};
复杂度分析:
时间复杂度:O(n)
空间复杂度:O(n)
解法二:迭代(广度优先遍历)
利用栈来记录比较的过程,实际上,递归就使用了调用栈,所以这里我们可以使用栈来模拟递归的过程
首先根的左右子树入栈 将左右子树出栈,比较两个数是否互为镜像 如果左右子树的根节点值相等,则将左子树的 left 、右子树的 right 、左子树的 right 、右子树的 left 依次入栈 继续出栈(一次出栈两个进行比较)……. 依次循环出栈入栈,直到栈为空
var isSymmetric = function(root) {
if(!root) return true
let stack = [root.left, root.right]
while(stack.length) {
let right = stack.pop()
let left = stack.pop()
if(left && right) {
if(left.val !== right.val) return false
stack.push(left.left)
stack.push(right.right)
stack.push(left.right)
stack.push(right.left)
} else if(left || right) {
return false
}
}
return true
};
复杂度分析:
时间复杂度:O(n)
空间复杂度:O(n)
2.将有序数组转换为二叉搜索树
将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。
本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。
示例:
给定有序数组: [-10,-3,0,5,9],
一个可能的答案是:[0,-3,9,-10,null,5],它可以表示下面这个高度平衡二叉搜索树:
0
/ \
-3 9
/ /
-10 5
深度优先遍历:
递归构建每个子树。将数组从最中间项分割得到三个部分:子数组1,中间项,子数组2。将中间项作为当前节点的val,对子数组1和子数组2分别递归执行原方法,得到的两个子树分别作为上一个节点的左子树与右子树
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {number[]} nums
* @return {TreeNode}
*/
var sortedArrayToBST = function(nums) {
if (!nums.length) return null;
const root = new TreeNode(null);
if(nums.length > 1) root.left = sortedArrayToBST(nums.splice(0, nums.length / 2));
root.val = nums[0];
root.right = sortedArrayToBST(nums.splice(1));
return root;
};
3.将有序数组转换为二叉搜索树
将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。
本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。
示例:
给定有序数组: [-10,-3,0,5,9],
一个可能的答案是:[0,-3,9,-10,null,5],它可以表示下面这个高度平衡二叉搜索树:
0
/ \
-3 9
/ /
-10 5
广度优先遍历:
var levelOrderBottom = function(root) {
if(!root)return [];
let queue=[root];
let res=[];
let level=0;
while(queue.length){
let temp=[];
res[level]=[];
for(let i=0;i<queue.length;i++){
res[level].push(queue[i].val);
if(queue[i].left){
temp.push(queue[i].left);
}
if(queue[i].right){
temp.push(queue[i].right);
}
}
queue=temp;
++level;
}
return res.reverse();
};
4.合并二叉树
给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠。
你需要将他们合并为一个新的二叉树。合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 NULL 的节点将直接作为新二叉树的节点。
示例 1:
输入:
Tree 1 Tree 2
1 2
/ \ / \
3 2 1 3
/ \ \
5 4 7
输出:
合并后的树:
3
/ \
4 5
/ \ \
5 4 7
思路:递归;
一:生成一个新的tree;
二:合并到的t1/t2上
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} t1
* @param {TreeNode} t2
* @return {TreeNode}
*/
var mergeTrees = function(t1, t2) {
if(t1 === null){
return t2
}else if(t2 === null){
return t1
}else{
t1.val = t1.val + t2.val
t1.left = mergeTrees(t1.left, t2.left)
t1.right = mergeTrees(t1.right, t2.right)
return t1
}
};
5.二叉树的最大深度
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。
示例:
给定二叉树 [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回它的最大深度 3 。
广度优先搜索
通过创建FIFO队列,迭代每一层元素,每迭代一层,level+1
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root
* @return {number}
*/
var maxDepth = function(root, level = 0) {
if (root === null) return 0
let queue = [root]
while(queue.length > 0) {
let len = queue.length
while(len--) { //把当前level的queue队列清空,同时把节点左右子节点push
let first = queue.shift() // 左出
if( first.left) queue.push(first.left) // 左子节点入队列
if( first.right) queue.push(first.right) //右子节点入队列
}
level++
}
return level
};
深度优先搜索
思路
找出终止条件:当前节点为空
找出返回值:节点为空时说明高度为0,所以返回0;节点不为空时则分别求左右子树的高度的最大值,同时加1表示当前节点的高度,返回该数值
某层的执行过程:在返回值部分基本已经描述清楚
时间复杂度:O(n)
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root
* @return {number}
*/
var maxDepth = function(root) {
if(!root) {
return 0;
} else {
const left = maxDepth(root.left);
const right = maxDepth(root.right);
return Math.max(left, right) + 1;
}
};
6.深度优先搜索
时间复杂度:O(n)
空间复杂度:O(n)
思路递归交换当前节点的左右节点,当节点为null时返回
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root
* @return {TreeNode}
*/
var invertTree = function(root) {
if(root === null) {
return null;
}
let right = invertTree(root.right);
let left = invertTree(root.left);
root.left = right;
root.right = left;
return root;
};
广度度优先搜索
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root
* @return {TreeNode}
*/
var invertTree = function(root) {
let queue = [root];
while(queue.length > 0){
let cur = queue.pop();
if(cur === null) continue;
[cur.left,cur.right] = [cur.right,cur.left];
queue.unshift(cur.left);
queue.unshift(cur.right);
}
return root;
};
7.二叉树展开为链表
给定一个二叉树,原地将它展开为一个单链表。
例如,给定二叉树
1
/ \
2 5
/ \ \
3 4 6
将其展开为:
1
\
2
\
3
\
4
\
5
\
6
深度优先:思路
1
/ \
2 5
/ \ \
3 4 6
//将 1 的左子树插入到右子树的地方
1
\
2 5
/ \ \
3 4 6
//将原来的右子树接到左子树的最右边节点
1
\
2
/ \
3 4
\
5
\
6
//将 2 的左子树插入到右子树的地方
1
\
2
\
3 4
\
5
\
6
//将原来的右子树接到左子树的最右边节点
1
\
2
\
3
\
4
\
5
\
6
var flatten = function(root) {
helper(root);
};
function helper(root){
if(root==null){
return null;
}
var temp;
//如果左节点不为空,说明需要移位操作
if(root.left!=null){
temp=root.right;
//把左节点挪到右节点
root.right=root.left;
root.left=null;
//找到最右下得节点
var right=findRight(root.right);
把之前拆解下的右节点接到最右下节点
right.right=temp;
}
//递归下一个右节点,知道最后
return helper(root.right);
}
//找到一个数的最右下节点
function findRight(root){
if(root.right==null){
return root;
}
return findRight(root.right);
}
8.不同的二叉搜索树
给定一个整数 n,求以 1 ... n 为节点组成的二叉搜索树有多少种?
示例:
输入: 3
输出: 5
解释:
给定 n = 3, 一共有 5 种不同结构的二叉搜索树:
1 3 3 2 1
\ / / / \ \
3 2 1 1 3 2
/ / \ \
2 1 2 3
动态规划:
每一个树的构成都可以分为左子树和右子树,所以以i为根节点的二叉搜索树种类的个数可以分解为子问题:左子树的种类 乘 右子树的种类。
左子树的节点范围为:1...i-1。右子树的节点范围为:i+1...n。
那么就可以把同样的问题转接到左子树和有字数上。
时间复杂度:O(n²)
空间复杂度:O(n)
/**
* @param {number} n
* @return {number}
*/
var numTrees = function(n) {
// res[i]表示以1...i为节点组成的二叉搜索树的种类
let res = new Array(n+1).fill(0);
res[0] = 1;
res[1] = 1;
// res[1]已经确定,因此从2开始
for (let i = 2; i <= n; i++) {
// j表示分别从1为根节点至i为根节点
for (let j = 1; j <= i; j++) {
res[i] += res[j-1] * res[i-j];
}
}
return res[n];
};