二叉树遍历框架
void traverse(TreeNode root) {
if (root == null) {
return;
}
// 前序位置
traverse(root.left);
// 中序位置
traverse(root.right);
// 后序位置
}
先不管所谓前中后序,单看 traverse 函数,你说它在做什么事情?
/* 迭代遍历数组 */
void traverse(int[] arr) {
for (int i = 0; i < arr.length; i++) {
}
}
/* 递归遍历数组 */
void traverse(int[] arr, int i) {
if (i == arr.length) {
return;
}
// 前序位置
traverse(arr, i + 1);
// 后序位置
}
/* 迭代遍历单链表 */
void traverse(ListNode head) {
for (ListNode p = head; p != null; p = p.next) {
}
}
/* 递归遍历单链表 */
void traverse(ListNode head) {
if (head == null) {
return;
}
// 前序位置
traverse(head.next);
// 后序位置
}
/* 递归遍历单链表,倒序打印链表元素 */
void traverse(ListNode head) {
if (head == null) {
return;
}
traverse(head.next);
// 后序位置
print(head.val);
}
结合上面那张图,你应该知道为什么这段代码能够倒序打印单链表了吧,本质上是利用递归的堆栈帮你实现了倒序遍历的效果。
那么说回二叉树也是一样的,只不过多了一个中序位置罢了。
前中后序是遍历二叉树过程中处理每一个节点的三个特殊时间点,绝不仅仅是三个顺序不同的 List:
使用图来进行理解:
注意:
二叉树的所有问题,就是让你在前中后序位置注入巧妙的代码逻辑,去达到自己的目的,你只需要单独思考每一个节点应该做什么,其他的不用你管,抛给二叉树遍历框架,递归会在所有节点上做相同的操作。
题目描述
解决方案
/**
* 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}
*/
// 定义:输入一个节点,返回以该节点为根的二叉树的最大深度
var maxDepth = function(root) {
if(root == null) return 0
let leftMax = maxDepth(root.left)
let rightMax = maxDepth(root.right)
// shi
return Math.max(leftMax, rightMax) + 1
};
题目描述
解决方案
/**
* 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[]}
*/
var preorderTraversal = function(root) {
let res = []
var traverse = function(node) {
if(node == null) return []
res.push(node.val)
traverse(node.left)
traverse(node.right)
}
traverse(root)
return res
};
题目描述
解决方案
所谓二叉树的直径,就是左右子树的最大深度之和,那么直接的想法是对每个节点计算左右子树的最大高度,得出每个节点的直径,从而得出最大的那个直径。
/**
* 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}
*/
var diameterOfBinaryTree = function(root) {
let maxDiameter = 0 // 定义一个变量 后续每次操作需要进行相应的更新
var maxDepth = function(root) {
if(root == null) return 0
let leftMax = maxDepth(root.left)
let rightMax = maxDepth(root.right)
maxDiameter = Math.max(maxDiameter, leftMax+rightMax) // 二叉树的最大直接直径,在程序运行的过程中就进行相应更新
return 1 + Math.max(leftMax, rightMax)
}
maxDepth(root)
return maxDiameter
};
无论使用哪一种思维模式,你都需要思考
题目描述
解决方案1
1、这题能不能用「遍历」的思维模式解决?
/**
* 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 {TreeNode}
*/
var invertTree = function(root) {
// 遍历的思路,清楚每一个节点应该做什么,清楚每一个节点应该做些什么
var traverse = function(root) {
if(root == null) return null
// 交换每个节点的左右节点
let temp = root.left
root.left = root.right
root.right = temp
// 遍历框架
traverse(root.left)
traverse(root.right)
}
traverse(root)
return root
};
解决方案2
2、这题能不能用「分解问题」的思维模式解决?
// 定义:将以 root 为根的这棵二叉树翻转,返回翻转后的二叉树的根节点
TreeNode invertTree(TreeNode root);
/**
* 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 {TreeNode}
*/
var invertTree = function(root) {
if(root == null) return null
let left = invertTree(root.left)
let right = invertTree(root.right)
// 将左右子树进行调换操作
root.left = right
root.right = left
return root
};
这种「分解问题」的思路,核心在于你要给递归函数一个合适的定义,然后用函数的定义来解释你的代码;如果你的逻辑成功自恰,那么说明你这个算法是正确的。
题目描述
解决方案
代码实现
/**
* // 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}
*/
var connect = function(root) {
if(root == null) return null
// 遍历三叉树
traverse(root.left,root.right)
return root
};
// 三叉树的遍历框架
var traverse = function(node1, node2) {
if(node1 == null || node2 == null) {
return
}
// 前序位置,将传入的两个节点连接起来
node1.next = node2
// 连接相同父节点的两个子节点
traverse(node1.left, node1.right)
traverse(node2.left, node2.right)
// 连接跨越父节点的两个子节点
traverse(node1.right, node2.left)
}
二叉树的构造问题一般都是使用「分解问题」的思路:构造整棵树 = 根节点 + 构造左子树 + 构造右子树。
题目链接
题目链接
题目描述
解决方案
/**
* 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 {number[]} nums
* @return {TreeNode}
*/
var constructMaximumBinaryTree = function(nums) {
return build(nums, 0, nums.length-1)
};
var build = function(nums, lo, hi) {
if(lo > hi) {
return null // 不符合情况的话,返回null
}
// 找到数组中的最大值,以及其对应的索引
let index = -1, maxVal = -100
for(let i = lo; i <= hi; i++) {
if(nums[i] > maxVal) {
index = i
maxVal = nums[i]
}
}
// 先构造出根节点
let root = new TreeNode(maxVal)
root.left = build(nums, lo, index-1)
root.right = build(nums, index+1, hi)
return root
}
题目链接
题目链接
题目描述
解决方案
/**
* 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 {number[]} preorder
* @param {number[]} inorder
* @return {TreeNode}
*/
var buildTree = function(preorder, inorder) {
return buildChildrenTree(preorder, 0, preorder.length-1, inorder, 0, inorder.length-1)
};
// 构造树的时候,因为要递归调用所以要把索引都列举出来
var buildChildrenTree = function(preorder, preStart, preEnd, inorder, inStart, inEnd) {
// 判断一个即可,因为前序和中序都是表示一个树的遍历情况
if(preStart > preEnd) return null
// 根节点其实就是前序遍历中的第一个节点
let index = -1 // 找节点
let rootVal = preorder[preStart]
for(let i = 0; i < inorder.length; i++) {
if(inorder[i] == rootVal) {
index = i
break
}
}
// 先构造出当前根节点
let leftSize = index - inStart
let root = new TreeNode(rootVal)
// 递归构造左右子树
root.left = buildChildrenTree(preorder, preStart+1, preStart+leftSize, inorder, inStart, index-1)
root.right = buildChildrenTree(preorder, preStart+leftSize+1, preEnd, inorder, index+1, inEnd)
return root
}
var buildTree = function(preorder, inorder) {
return buildChildrenTree(preorder, 0, preorder.length-1, inorder, 0, inorder.length-1)
};
// 构造树的时候,因为要递归调用所以要把索引都列举出来
var buildChildrenTree = function(preorder, preStart, preEnd, inorder, inStart, inEnd) {
// 判断一个即可,因为前序和中序都是表示一个树的遍历情况
if(preStart > preEnd) return null
// 根节点其实就是前序遍历中的第一个节点
// let index = -1 // 找节点
let rootVal = preorder[preStart]
// 存储inorder中值到索引的映射
let m = new Map()
for(let i = 0; i < inorder.length; i++) {
// m.push([inorder[i], i])
m[inorder[i]] = i
}
console.log(m)
let index = m[rootVal]
// for(let i = 0; i < inorder.length; i++) {
// if(inorder[i] == rootVal) {
// index = i
// break
// }
// }
// 先构造出当前根节点
let leftSize = index - inStart
let root = new TreeNode(rootVal)
// 递归构造左右子树
root.left = buildChildrenTree(preorder, preStart+1, preStart+leftSize, inorder, inStart, index-1)
root.right = buildChildrenTree(preorder, preStart+leftSize+1, preEnd, inorder, index+1, inEnd)
return root
}
题目描述
解决方案
/**
* 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 {number[]} inorder
* @param {number[]} postorder
* @return {TreeNode}
*/
var buildTree = function(inorder, postorder) {
return buildPostChildren(inorder, 0, inorder.length-1, postorder, 0, postorder.length-1)
};
var buildPostChildren = function(inorder, inStart, inEnd, postorder, postStart, postEnd){
if(inStart > inEnd) return null
// 根节点就是后续遍历的最后一个节点
let index = -1
let rootVal = postorder[postEnd]
for(let i = inStart; i <= inEnd; i++) {
if(inorder[i] == rootVal) {
index = i
}
}
let leftSize = index - inStart
// 先构造初根节点
let root = new TreeNode(rootVal)
root.left = buildPostChildren(inorder, inStart, index-1, postorder, postStart, postStart+leftSize-1)
root.right = buildPostChildren(inorder, index+1, inEnd, postorder, postStart+leftSize, postEnd-1)
return root
}