二叉树(JS)
1.翻转二叉树(leetCode226)
var invertTree = function(root) {
if(root === null) {return null}
invertTree(root.left)
invertTree(root.right)
let temp = root.left
root.left = root.right
root.right = temp
return root
};
2.二叉树展开为链表(leetCode114)
var flatten = function(root) {
if(root === null) {return }
flatten(root.left)
flatten(root.right)
let left = root.left
let right = root.right
root.left = null
root.right = left
let p = root
while(p.right != null) {
p = p.right
}
p.right = right
return root
};
3.填充每个节点的下一个右侧节点指针(leetCode116)
var connect = function(root) {
if(root === null) {return null}
const helper = (node1, node2) => {
if(node1 === null || node2 === null) {return }
node1.next = node2
helper(node1.left, node1.right)
helper(node2.left, node2.right)
helper(node1.right, node2.left)
}
helper(root.left, root.right)
return root
};
4.最大二叉树(leetCode654)
var constructMaximumBinaryTree = function(nums) {
const helper = (arr, start, end) => {
if(start > end) {return null}
let index = -1
let maxVal = -1
for(let i = start; i <= end; i++) {
const n = arr[i]
if(n > maxVal) {
index = i
maxVal = n
}
}
let root = new TreeNode(maxVal)
root.left = helper(arr, start, index-1)
root.right = helper(arr, index+1, end)
return root
}
let root = helper(nums, 0, nums.length-1)
return root
};
5.从前序与中序遍历构造二叉树(leetCode105)
var buildTree = function(preorder, inorder) {
const build = (preorder, preStart, preEnd, inorder, inStart, inEnd) => {
if(preStart > preEnd) {return null}
let rootVal = preorder[preStart]
let index = inorder.indexOf(rootVal)
let root = new TreeNode(rootVal)
let leftsize = index - inStart
root.left = build(preorder, preStart+1, preStart+leftsize, inorder, inStart, index-1)
root.right = build(preorder, preStart+leftsize+1, preEnd, inorder, index+1, inEnd)
return root
}
return build(preorder, 0, preorder.length-1, inorder, 0, inorder.length-1)
};
6.从中序与后序遍历构造二叉树(leetCode106)
var buildTree = function(inorder, postorder) {
const build = (inorder, inStart, inEnd, postorder, postStart, postEnd) => {
if(postEnd < postStart) {return null}
const rootVal = postorder[postEnd]
const index = inorder.indexOf(rootVal)
const leftsize = index - inStart
const root = new ListNode(rootVal)
root.left = build(inorder, inStart, index-1, postorder, postStart, postStart+leftsize-1)
root.right = build(inorder, index+1, inEnd, postorder, postStart+leftsize, postEnd-1)
return root
}
return build(inorder, 0, inorder.length-1, postorder, 0, postorder.length-1)
};
7.寻找重复的子树(leetCode652)
var findDuplicateSubtrees = function(root) {
let memo = new Map()
let res = []
const helper = (root) => {
if(root === null) {
return '#'
}
const left = helper(root.left)
const right = helper(root.right)
const subtree = left + ',' + right + ',' + root.val
if(memo.has(subtree)) {
memo.set(subtree, memo.get(subtree) + 1)
}else {
memo.set(subtree, 1)
}
if(memo.get(subtree) === 2) {
res.push(root)
}
return subtree
}
helper(root)
return res
};
二叉搜索树
8.BST第k小的元素(leetCode230)
var kthSmallest = function(root, k) {
let res = []
const traverse = (root) => {
if(!root) {return}
traverse(root.left)
res.push(root.val)
traverse(root.right)
}
traverse(root)
return res[k-1]
};
9.二叉搜索数转化累加树(leetCode538)
var convertBST = function(root) {
let sum = 0
const traverse = (root) => {
if(!root) {return }
traverse(root.right)
sum += root.val
root.val = sum
traverse(root.left)
}
traverse(root)
return root
};
10.BST转累加树(leetCode1038)
var bstToGst = function(root) {
let sum = 0
const traverse = (root) => {
if(!root) {return}
traverse(root.right)
sum += root.val
root.val = sum
traverse(root.left)
}
traverse(root)
return root
};
11.删除二叉搜索树中的节点(leetCode450)
var deleteNode = function(root, key) {
if(!root) {return null}
//定义找到BST右子树最小节点的方法
const getMin = (node) => {
while(node.left != null) {
node = node.left
}
return node
}
//搜索BST
if(key < root.val) {
root.left = deleteNode(root.left, key)
}else if(key > root.val) {
root.right = deleteNode(root.right, key)
}else {
//分析前两种情况
if(root.left === null) {
return root.right
}else if(root.right === null) {
return root.left
}else {
//处理复杂的第三种情况
let minNode = getMin(root.right)
root.right = deleteNode(root.right, minNode.val)
minNode.left = root.left
minNode.right = root.right
root = minNode
}
}
return root
};
12.二叉搜索树中的插入操作(leetCode701)
var insertIntoBST = function(root, val) {
if(root === null) return new TreeNode(val)
if(root.val > val) {
root.left = insertIntoBST(root.left, val)
}else if(root.val < val) {
root.right = insertIntoBST(root.right, val)
}
return root
};
13.二叉搜索树中的搜索(leetCode700)
var searchBST = function(root, val) {
if(!root) {return null}
if(root.val > val) {
return searchBST(root.left, val)
}
if(root.val < val) {
return searchBST(root.right, val)
}
return root
};
如果在一颗普通的二叉树中寻找,可以这样写代码:
var searchBST = function(root, val) {
if(!root) {return null}
if(root.val === val) {
return root
}
let left = searchBST(root.left, val)
let right = searchBST(root.right, val)
return left != null ? left : right
}
14.验证二叉搜索树(leetCode98)
我们通过使用辅助函数,增加函数参数列表,在参数中携带额外信息,将这种约束传递给子树的所有节点,这也是二叉树算法的一个小技巧吧。
var isValidBST = function(root) {
const helper = (root, min, max) => {
if(!root) return true
if(min != null && root.val <= min.val) {
return false
}
if(max != null && root.val >= max.val) {
return false
}
return helper(root.left, min, root) && helper(root.right, root, max)
}
return helper(root, null, null)
};
15.不同的二叉搜索树(leetCode96)
步骤:
-
定义备忘录,消除重叠子问题。为什么会出现重叠子问题呢?
例如,i=1时。left=count(1,0)=1 right=count(2,5) i=2时,left=count(1,1) right=count(3,5) 但是count(1,1)的left=count(1,0),right=(2,1) 所以出现了两次count(1,0)。这样的重叠子问题还有很多。为了能够提高效率,我们要设置备忘录。
-
设置base case,当lo>high时意味者两种情况。lo为根节点,左子树为null;第二种情况是hi为根节点,右子树为null。这两种情况count均返回1。
-
其他情况,count返回的结果为左子树数量乘以右子树数量
var numTrees = function(n) { let memo = new Array(n+1).fill(0).map(() => new Array(n+1).fill(0)) const count = (lo, hi) => { if(lo > hi) return 1 if(memo[lo][hi] != 0) { return memo[lo][hi] } let res = 0 for(let i = lo; i <= hi; i++) { const left = count(lo, i-1) const right = count(i+1, hi) res += left * right } memo[lo][hi] = res return res } return count(1, n) };
16.不同的二叉搜索树2(leetCode95)
明白了上道题构造合法 BST 的方法,这道题的思路也是一样的:
1、穷举root
节点的所有可能。
2、递归构造出左右子树的所有合法 BST。
3、给root
节点穷举所有左右子树的组合。
var generateTrees = function(n) {
if(n === 0) {return []}
const build = (lo, hi) => {
let res = []
if(lo > hi) {
res.push(null)
return res
}
for(let i = lo; i <= hi; i++) {
let leftTree = build(lo, i-1)
let rightTree = build(i+1, hi)
for(let left of leftTree ) {
for(let right of rightTree) {
root = new TreeNode(i)
root.left = left
root.right = right
res.push(root)
}
}
}
return res
}
return build(1,n)
};
17.二叉搜索子树的最大键值和(leetCode1373)
18.二叉树的序列化和反序列化(leetCode297)
19.扁平化嵌套列表迭代器(leetCode341)
题目要求是:
NestedIterator(List nestedList) 用嵌套列表 nestedList 初始化迭代器。
int next() 返回嵌套列表的下一个整数。
boolean hasNext() 如果仍然存在待迭代的整数,返回 true ;否则,返回 false 。
思路:
- 定义一个dealData函数将多维数组转化成一维数组,调用该函数。设置arr和index属性
- 在原型上设置hasNext函数,若index小于length则为true
- 在原型上设置next函数,返回this.arr[this.index++]
/**
* @constructor
* @param {NestedInteger[]} nestedList
*/
var NestedIterator = function (nestedList) {
// 通过参数说明我们可以知道nestedList的类型是NestedInteger的数组Ï
const arr = [];
// 多维数组递归转一维数组
function dealData(nestedList) {
for (let i = 0; i < nestedList.length; i++) {
if (nestedList[i].isInteger()) {
arr.push(nestedList[i].getInteger())
} else {
dealData(nestedList[i].getList());
}
}
}
// 调用我们写的递归函数
dealData(nestedList);
this.arr = arr;
this.index = 0;
};
NestedIterator.prototype.hasNext = function () {
return this.index < this.arr.length;
};
NestedIterator.prototype.next = function () {
return this.arr[this.index++];
};
20.二叉树的最近公共祖先(leetCode236)
到任何递归型的问题,无非就是灵魂三问:
1、这个函数是干嘛的?
首先看第一个问题,这个函数是干嘛的?或者说,你给我描述一下lowestCommonAncestor这个函数的「定义」吧。
描述:给该函数输入三个参数root,p,q,它会返回一个节点。
情况 1,如果p和q都在以root为根的树中,函数返回的即使p和q的最近公共祖先节点。
情况 2,那如果p和q都不在以root为根的树中怎么办呢?函数理所当然地返回null呗。
情况 3,那如果p和q只有一个存在于root为根的树中呢?函数就会返回那个节点。
题目说了输入的p和q一定存在于以root为根的树中,但是递归过程中,以上三种情况都有可能发生,所以说这里要定义清楚,后续这些定义都会在代码中体现。
2、这个函数参数中的变量是什么?
函数参数中的变量是root,因为根据框架,lowestCommonAncestor(root)会递归调用root.left和root.right;至于p和q,我们要求它俩的公共祖先,它俩肯定不会变化的。
第二个问题也解决了,你也可以理解这是「状态转移」,每次递归在做什么?不就是在把「以root为根」转移成「以root的子节点为根」,不断缩小问题规模嘛?
3、得到函数的递归结果,你应该干什么?
先想 base case,如果root为空,肯定得返回null。如果root本身就是p或者q,比如说root就是p节点吧,如果q存在于以root为根的树中,显然root就是最近公共祖先;即使q不存在于以root为根的树中,按照情况 3 的定义,也应该返回root节点。
最后,是使用递归调用的left和right搞事,分情况讨论:
情况 1,如果p和q都在以root为根的树中,那么left和right一定分别是p和q(从 base case 看出来的)。
情况 2,如果p和q都不在以root为根的树中,直接返回null。
情况 3,如果p和q只有一个存在于root为根的树中,函数返回该节点。
代码流程:
- base case分析,root === null的情况以及root === p || root === q的情况
- 选用后序遍历,后序遍历可理解为从下往上走,先遍历后再进行其他逻辑判断。
- 遍历完成后,最后开始其他逻辑判断。
var lowestCommonAncestor = function(root, p, q) {
if(!root) {return null}
if(root === p || root === q) return root
const left = lowestCommonAncestor(root.left, p, q)
const right = lowestCommonAncestor(root.right, p, q)
if(left != null && right != null) {
return root
}
if(left === null && right === null) {
return null
}
return left === null ? right : left
};
21.完全二叉树的节点个数(leetCode222)
**完全二叉树:**每一层都是紧凑靠左排列的。
**满二叉树:**是一种特殊的完全二叉树,每层都是是满的,像一个稳定的三角形。
1.求普通二叉树的节点个数:
function countNodes(root) {
if(root === null) return 0
return 1 + countNodes(root.left) + countNodes(root.right)
}
2.求满二叉树的节点个数:满二叉树的节点个数和树的高度呈指数关系
function countNodes(root) {
let h = 0
while(root != null) {
root = root.left
h++
}
return Math.pow(2, h) - 1
}
3.完全二叉树的节点个数则是将两者结合起来:
var countNodes = function(root) {
let l = root, r = root
let hl = 0, hr =0
while(l != null) {
l = l.left
hl++
}
while(r != null) {
r = r.right
hr++
}
if(hl === hr) {
return Math.pow(2, hl) - 1
}else {
return 1 + countNodes(root.left) + countNodes(root.right)
}
};
22.面试提问:如何将递归改成迭代算法
如下是迭代遍历二叉树的完整代码框架:
- 代码中最有技巧性的是这个
visited
指针,它记录最近一次遍历完的子树根节点(最近一次pop
出栈的节点),我们可以根据对比p
的左右指针和visited
是否相同来判断节点p
的左右子树是否被遍历过,进而分离出前中后序的代码位置。 visited
指针初始化指向一个新 new 出来的二叉树节点,相当于一个特殊值,目的是避免和输入二叉树中的节点重复。
var traverse = function(root) {
//左侧树枝一撸到底
const pushLeftBranch = (p) => {
while(p != null) {
/*******************/
/** 前序遍历代码位置 **/
/*******************/
stack.push(p)
p = p.left
}
}
// 模拟函数调用栈
let stack = []
// 指向上一次遍历完的子树根节点
let visited = new TreeNode(-1)
// 开始遍历整棵树
pushLeftBranch(root)
// 判断stack是否还有元素
while(!(stack.length === 0)) {
// 得到栈顶元素
let p = stack[stack.length - 1]
// p的左子树被遍历完了,且右子树没有被遍历过
if((p.left === null || p.left === visited)
&& p.right != visited) {
/*******************/
/** 中序遍历代码位置 **/
/*******************/
// 去遍历 p 的右子树
pushLeftBranch(p.right)
}
// p 的右子树被遍历完了
if(p.right === null || p.right === visited) {
/*******************/
/** 后序遍历代码位置 **/
/*******************/
// 以 p 为根的子树被遍历完了,出栈
// visited 指针指向 p
postorder.push(p.val)
visited = stack.pop()
}
}
return postorder
};
如下是后序遍历的迭代算法示例:
var postorderTraversal = function(root) {
let stack = []
let postorder = []
let visited = new TreeNode(-1)
const pushLeftBranch = (p) => {
while(p != null) {
stack.push(p)
p = p.left
}
}
pushLeftBranch(root)
while(!(stack.length === 0)) {
let p = stack[stack.length - 1]
console.log(p)
if((p.left === null || p.left === visited)
&& p.right != visited) {
pushLeftBranch(p.right)
}
if(p.right === null || p.right === visited) {
postorder.push(p.val)
visited = stack.pop()
}
}
return postorder
};