本文主要记录一些二叉树的基本算法,比较简单的入门的算法,比如【前中序遍历,最大直径,最大深度,翻转,拉平,填充】等总共8道题,每道题基本上都有2种方式,感兴趣的可以看看。对编程思考还是比较有好处的。也可以用于你面试之前的快速回忆!建议收藏❤️
- 遍历二叉树- 回溯算法
- 分解子问题- 动态规划
动态规划(Dynamic Programming,DP): 是运筹学的一个分支,通过分解问题,求解[决策过程]最优化的过程。
回溯算法: 回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。回溯法是一种选优[搜索]法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。
1. 二叉树的前序遍历
方式一:动态规划
分解成子问题
var preorderTraversal = function(root) {
// 动态规划
let res = [];
if(!root){
return [];
}
res.push(root.val);
res = res.concat(preorderTraversal(root.left));
res = res.concat(preorderTraversal(root.right));
return res;
};
方式二:回溯算法
外部变量 + 辅助函数
var preorderTraversal = function(root) {
// 回溯算法
let res = [];
const traverse = ((root)=>{
if(!root){
return;
}
res.push(root.val);
traverse(root.left);
traverse(root.right);
})
traverse(root);
return res;
};
2. 二叉树的中序遍历
方式一:动态规划
var inorderTraversal = function(root) {
// 动态规划 分解
var res =[];
if(!root){
return res;
}
res = res.concat(inorderTraversal(root.left));
res.push(root.val);
res = res.concat(inorderTraversal(root.right));
return res;
};
方式二:回溯算法
二叉树遍历函数 + 外部变量
var inorderTraversal = function(root) {
// 回溯算法 辅助函数 + 变量
var res =[];
const traverse = ((root)=>{
if(!root){
return;
}
traverse(root.left);
res.push(root.val);
traverse(root.right);
})
traverse(root);
return res;
};
3. 二叉树的后序遍历
方式一:动态规划
遍历二叉树
var postorderTraversal = function(root) {
let res = [];
if(!root){
return res;
}
res = res.concat(postorderTraversal(root.left));
res = res.concat(postorderTraversal(root.right));
res.push(root.val);
return res;
};
方式二:回溯算法
辅助函数 + 外部变量
var postorderTraversal = function(root) {
// 遍历二叉树
let res = [];
const traverse =((root)=>{
if(!root){
return;
}
traverse(root.left);
traverse(root.right);
res.push(root.val);
})
traverse(root);
return res;
};
4. 求二叉树的最大深度
方式一: 动态规划(广度优先 BFS) ⭐️⭐️
var maxDepth = function(root) {
// 动态规划
if(!root){
// 递归结束条件
return 0;
}
const left = maxDepth(root.left);
const right = maxDepth(root.right);
return 1 + Math.max(left, right);
};
方式二:回溯算法(深度优先 DFS)
外部变量 + 辅助函数
function maxDepth(root) {
let depth = 0;
let res = 0;
// 遍历二叉树
function traverse(root) {
if (root === null) {
return;
}
// 前序遍历位置
depth++;
// 遍历的过程中记录最大深度
res = Math.max(res, depth);
traverse(root.left);
traverse(root.right);
// 后序遍历位置
depth--;
}
traverse(root);
return res;
}
5. 翻转二叉树
方式一: 分解
var invertTree = function(root) {
// 动态规划
if(!root){
return null;
}
// 先翻转左边
// 再翻转右边
let left = invertTree(root.left);
let right = invertTree(root.right);
root.left = right;
root.right = left;
return root;
};
方式二:遍历
var invertTree = function(root) {
// 遍历
if(!root){
return null;
}
// 左右交换
var temp = root.right;
root.right = root.left;
root.left = temp;
invertTree(root.left);
invertTree(root.right);
return root;
};
6. 二叉树的直径
方式一:动态规划
var diameterOfBinaryTree = function(root) {
var maxDiameter = 0;
var maxDepth = function(root){
if(!root){
return 0 ;
}
const left = maxDepth(root.left);
const right = maxDepth(root.right);
// 后序位置
maxDiameter = Math.max(maxDiameter, left+right);
return 1 + Math.max(left, right);
}
maxDepth(root);
return maxDiameter;
};
方式二:复杂度比较高,不推荐❌
var BadSolution = function() {};
BadSolution.prototype.diameterOfBinaryTree = function(root) {
if (root == null) {
return 0;
}
// 计算出左右子树的最大高度
let leftMax = this.maxDepth(root.left);
let rightMax = this.maxDepth(root.right);
// root 这个节点的直径
let res = leftMax + rightMax;
// 递归遍历 root.left 和 root.right 两个子树
return Math.max(res,
Math.max(this.diameterOfBinaryTree(root.left),
this.diameterOfBinaryTree(root.right)));
};
BadSolution.prototype.maxDepth = function(root) {
if (root == null) {
return 0;
}
let leftMax = this.maxDepth(root.left);
let rightMax = this.maxDepth(root.right);
return 1 + Math.max(leftMax, rightMax);
};
7. 114. 二叉树展开为链表
方式一:分解方式
⚠️只有分解的方式(因为题目希望我们原地拉平,而且不希望我们有返回值,所以不能用遍历)
/**
* @param {TreeNode} root
* @return {void} Do not return anything, modify root in-place instead.
*/
var flatten = function(root) {
if(!root){
return [];
}
// 先拉平
flatten(root.left);
flatten(root.right);
// 先把左右存起来
const left = root.left;
const right = root.right;
// 重置左右的值
root.left = null;
root.right = left;
//
var p = root; // p 不是链表结构,所以没有next,但是有right
while(p.right){
p = p.right;
}
p.right = right;
};
方式二:遍历方式(但不符合此题目要求)
// 虚拟头节点,dummy.right 就是结果
var dummy = new TreeNode(-1);
// 用来构建链表的指针
var p = dummy;
function traverse(root) {
if (root == null) {
return;
}
// 前序位置
p.right = new TreeNode(root.val);
p = p.right;
traverse(root.left);
traverse(root.right);
}
8. 填充每个节点的下一个右侧节点指针
方式一:遍历三叉树
⚠️ 只有遍历方式
var connect = function(root) {
if (root === null) return null;
// 三叉数 遍历框架
const traverse = ((node1, node2)=>{
if(!node1 || !node2){
return
}
node1.next = node2;
traverse(node1.left, node1.right);
traverse(node1.right, node2.left);
traverse(node2.left, node2.right);
})
// 遍历「三叉树」,连接相邻节点
traverse(root.left, root.right);
return root;
};