1.递归序:在递归过程中每个节点都有三次访问的机会(用不用取决于你)
function f(head) {
if(!head) return null
// 先序访问head
f(head.left)
// 中序访问head
f(head.right)
// 后序访问head
}
// 递归序 1 2 4 4 4 2 5 5 5 2 1 3 6 6 6 3 7 7 7 3 1
// 先序 1 2 4 5 3 6 7 (第一次出现打印)
// 中序 4 2 5 1 6 3 7 (第二次出现打印)
// 后序 4 5 2 6 7 3 1 (第三次出现打印)
- 非递归先序:根左右 ==求导==> 根右左
function f(head) {
if(!head) return
let stack = []
let cur = head
stack.push(cur)
while (stack.length) {
cur = stack.pop()
console.log(cur.value)
if(cur.right) stack.push(cur.right)
if(cur.left) stack.push(cur.left)
}
}
3.非递归后序:左右根 ==颠倒==> 根右左 ==求导==> 根左右
function f(head) {
if(!head) return
let stack1 = []
let stack2 = []
let cur = head
stack1.push(cur)
while (stack1.length) {
let cur = stack1.pop()
stack2.push(cur)
if(cur.left) stack1.push(cur.left)
if(cur.right) stack1.push(cur.right)
}
while (stack2.length) {
console.log(stack2.pop().value)
}
}
4.非递归中序:左根右 ==左边界分解==> 左根(左根(左根(左根右)))
function f(head) {
if(!head) return
let stack = []
let cur = head
while (stack.length||cur) {
if(cur){
stack.push(cur)
cur = cur.left
}else{
cur = stack.pop()
console.log(cur.value)
cur = cur.right
}
}
}
5.求树的最大宽度(BFS),使用队列Queue
// 先来介绍宽度优先遍历BFS
function f(head){
if(!head) return
let queue = [head]
let leaf = false // 叶节点开关
while(queue.length){
head = queue.shift()
console.log(head.value)
head.left && queue.push(head.left)
head.right && queue.push(head.right)
if(!head.left||!head.right) leaf = true
}
}
// 1.使用hash表
function f(head) {
if(!head) return 0
let queue = [head]
let levelMap = new Map() // 节点所在层map
levelMap.set(head,0)
let level = 0 // 迭代层次
let count = 0
let max = 0
while (queue.length) {
head=queue.shift()
let curLevel=levelMap.get(head)
if(curLevel===level){
count++ // 当前层
}
else{ // 结算上一层,并置count为1,level++
max = Math.max(max,count)
level++
count=1
}
if(head.left){
queue.push(head.left)
levelMap.set(head.left,level+1)
}
if(head.right){
queue.push(head.right)
levelMap.set(head.right,level+1)
}
}
max = Math.max(max,count) // 最后再结算一次
return max
}
// 2.不用hash表
function f(head) {
if(!head) return 0
let queue = [head]
let curEnd = head // 当前层最后节点
let nextEnd = null // 下一次最后节点
let max = 0
let count = 0
while (queue.length) {
head = queue.shift()
count++
if(head.left) {
nextEnd = head.left
queue.push(head.left)
}
if(head.right){
nextEnd = head.right
queue.push(head.right)
}
if(head===curEnd) {
max = Math.max(max,count)
count=0
curEnd = nextEnd
}
}
return max
}
6.判断搜索二叉树:右子树任意节点值>=根值>=左子树的任意节点值,使用中序
// 递归
function f(head) {
let preVal = -Infinity
return (function isBst(node) {
if(!node) return true
if(!isBst(node.left)) return false // 左树判断
if(node.value<preVal) return false // 当前节点判断
preVal=node.value
return isBst(node.right) // 右树判断
})(head)
}
// 简单版
function f(head){
let stack = []
// 递归
(function f2(node,stack){
if(!node) return
f2(node.left,stack)
stack.push(node.value)
f2(node.right,stack)
})(head,stack)
for(let i=stack.length-1;i>0;i--) if(stack[i]<stack[i-1]) return false
return true
// 非递归
let preVal = -Infinity
while(stack.length||head){
if(head){
stack.push(head)
head = head.left
}else{
head = stack.pop()
if(head.value<preVal) return false
else preVal=head.value
head=head.right
}
}
return true
}
7.判断完全二叉树(BFS)
// 有右无左返false
// 当前节点为叶节点,后续节点不为叶节点返false
function f(head) {
if(!head) return true
let leaf = false // 叶节点开关
let l=r=null
let stack = [head]
while(stack.length){
head = stack.shift()
l=head.left
r=head.right
if((!l&&r) || (leaf&&(l||r))) return false
if(!l||!r) leaf=true
l&&stack.push(l)
r&&stack.push(r)
}
return true
}
10.二叉树套路:可以用于解决树型DP问题
模板
function f(head){
return (function(node){
if(!node) return null
let l = arguments.callee(node.left)
let r = arguments.callee(node.right)
let key = F(l, r, node)
// ...
let keyN = Fn(l, r, node)
return { key, ..., keyN }
})(head).key
}
// 1.判断是否是搜索二叉树
// 对于当前节点来说:左右子树必须是搜索二叉树且左子树最大节点值<=当前节点,右子树最小节点值>=当前节点
function f(node) {
return (function (node) {
if (!node) return null
let l = arguments.callee(node.left)
let r = arguments.callee(node.right)
let max = r ? r.max : node.value
let min = l ? l.min : node.value
let isBst = (l ? l.isBst && l.value <= node.value : true) && (r ? r.isBst && r.value >= node.value : true)
return { isBst, max, min }
})(node).isBst
}
// 2.判断是否是满二叉树:nodes = 2^h - 1 = 1<<h -1
function f(node) {
let root = (function (node) {
if (!node) return null
let l = arguments.callee(node.left)
let r = arguments.callee(node.right)
let nodes = (l ? l.nodes : 0) + (r ? r.nodes : 0) + 1
let height = Math.max(l ? l.height : 0, r ? r.height : 0) + 1
return { nodes, height }
})(node)
return root.nodes === (1 << root.height - 1)
}
// 3.判断平衡二叉树,左右平衡且高度差小于等于1
function f(node) {
return (function (node) {
if (!node) return null
let l = arguments.callee(node.left)
let r = arguments.callee(node.right)
let height = Math.max(l ? l.height : 0, r ? r.height : 0) + 1
let isBalance = l.isBalance && r.isBalance && Math.abs(l.height - r.height) <= 1
return { isBalance, height }
})(node).isBalance
}
11.求n1和n2的最低公共祖先
// 遍历所有节点,使用map记录父节点,然后通过map实现自低向上遍历,将遍历过的节点放入set
function f(h,n1,n2) {
let fMap = new Map()
fMap.set(head,null)
;(function (node) {
if(node.left){
fMap.set(node.left,node)
arguments.callee(node.left)
}
if(node.right){
fMap.set(node.right,node)
arguments.callee(node.right)
}
})(h)
let set = new Set()
while (fMap.get(n1)) {
set.add(n1)
n1 = fMap.get(n1)
}
while (fmap.get(n2)) {
if(set.has(n2)) return n2
set.add(n2)
n2 = fMap.get(n2)
}
return h // 尽头是head
}
// 自上而下
function f(node,n1,n2) {
if(!node||n1===node||n2===node) return node // 临界点
let l = arguments.callee(node.left,n1,n2)
let r = arguments.callee(node.right,n1,n2)
if(l&&r) return node // 左右有值返自身
return l||r // 返回有值的节点
}
// 序列化
function f1(node) { // 先序
if(!node) return '#-'
return `${node.value}-${arguments.callee(node.left)+arguments.callee(node.right)}`
}
// 反序列化
function f2(str) {
let arr = str.split('-')
arr.pop()
process(arr)
}
function process(arr) {
let str = arr.shift()
if(str==='#') return null
let node = {value:+str} // 先序
node.left = arguments.callee(arr)
node.right = arguments.callee(arr)
return node
}