二叉树是树形结构的一个重要类型。二叉树是指树中节点的度不大于2的有序树,它是一种最简单且最重要的树,经常在算法中有灵活而巧妙的应用,是算法面试中的常客,也是众多数据结构的基石。
本系列博文将通过一些力扣算法题目学习TypeScirpt,这篇将以二叉树为主题边学习TypeScipt边实战算法。(部分算法思想参考于程序员Carl:代码随想录)
使用TS定义一个二叉树类TreeNode,类中包括val值,左子树和右子树。子树有左右之分,且左右不能颠倒,但可以为空
class TreeNode {
public val: number;
public left: TreeNode | null;
public right: TreeNode | null;
constructor(val?: number, left?: TreeNode, right?: TreeNode) {
this.val = val === undefined ? 0 : val;
this.left = left === undefined ? null : left;
this.right = right === undefined ? null : right;
}
}
二叉树的遍历方式主要有:先序遍历、中序遍历、后序遍历、层次遍历。先序、中序、后序其实指的是父节点被访问的次序。若在遍历过程中,父节点先于它的子节点被访问,就是先序遍历;父节点被访问的次序位于左右孩子节点之间,就是中序遍历;访问完左右孩子节点之后再访问父节点,就是后序遍历。
很简单,设计一个traverse遍历函数,不断递归调用,将val值push进去即可
前序遍历
function preorderTraversal(root: TreeNode | null): number[] {
function traverse(root: TreeNode | null, res:number[]): void{
if(root === null) return ;
else{
res.push(root.val);
traverse(root.left, res);
traverse(root.right, res);
}
}
let res: number[] = [];
traverse(root, res);
return res;
};
中序遍历:
function inorderTraversal(root: TreeNode | null): number[] {
function traverse(root: TreeNode | null, res:number[]): void{
if(root === null) return ;
else{
traverse(root.left, res);
res.push(root.val);
traverse(root.right, res);
}
}
let res: number[] = [];
traverse(root, res);
return res;
};
后序遍历:
function postorderTraversal(root: TreeNode | null): number[] {
function traverse(root: TreeNode | null, res:number[]): void{
if(root === null) return ;
else{
traverse(root.left, res);
traverse(root.right, res);
res.push(root.val);
}
}
let res: number[] = [];
traverse(root, res);
return res;
};
使用到栈的结构
先序遍历,根结点入栈,出栈,然后右子树入栈,左子树入栈出栈
function preorderTraversal(root: TreeNode | null): number[] {
let res: number[] = [];
let myStack: TreeNode[] = [];
if(root == null) return [];
myStack.push(root);
while(myStack.length > 0){
let tmpNode = myStack.pop();
res.push(tmpNode.val);
if(tmpNode.right !== null)
myStack.push(tmpNode.right);
if(tmpNode.left !== null)
myStack.push(tmpNode.left);
}
return res;
};
后序遍历,做了一个reverse来实现:
function postorderTraversal(root: TreeNode | null): number[] {
let res: number[] = [];
let myStack: TreeNode[] = [];
if(root == null) return [];
myStack.push(root);
while(myStack.length > 0){
let tmpNode = myStack.pop();
res.push(tmpNode.val);
if(tmpNode.left !== null)
myStack.push(tmpNode.left);
if(tmpNode.right !== null)
myStack.push(tmpNode.right);
}
return res.reverse();
};
中序遍历,左子树不为空就不断压栈左结点,如左子树为空,则输出当前节点,再去压栈右结点
function inorderTraversal(root: TreeNode | null): number[] {
let res: number[] = [];
let myStack: TreeNode[] = [];
if(root === null) return [];
let tmpNode: TreeNode | null = root;
while(tmpNode !== null || myStack.length > 0){
// 左子树为空 弹栈输出 然后进右子树
if(tmpNode == null){
tmpNode = myStack.pop()!;
res.push(tmpNode.val);
tmpNode = tmpNode.right;
}
// 左子树不为空时 先将左结点压栈
else{
myStack.push(tmpNode);
tmpNode = tmpNode.left;
}
}
return res;
};
由上到下,力扣链接:https://leetcode.cn/problems/binary-tree-level-order-traversal/
利用到队列的特性,一层一层的入队列,本层在出队列的同时,将出的这个结点的左右子树(下一层)入队列:
function levelOrder(root: TreeNode | null): number[][] {
let res: number[][] = [];
let tmpList: TreeNode[] = [];
if(root !== null) tmpList.push(root);
while(tmpList.length > 0){
let tmpArray: number[] = [];
for(let i = 0, length = tmpList.length; i < length; i++){
let tmpNode: TreeNode = tmpList.shift()!;
tmpArray.push(tmpNode.val);
if(tmpNode.left !== null){
tmpList.push(tmpNode.left);
}
if(tmpNode.right !== null){
tmpList.push(tmpNode.right);
}
}
res.push(tmpArray);
}
return res;
};
由下到上:跟由上到下原理相同,最后做一个reverse就好了:return res.reverse();
若推广到N叉树,在出栈时就把本结点的下一层的所有子结点入栈,即:
/**
* Definition for node.
* class Node {
* val: number
* children: Node[]
* constructor(val?: number) {
* this.val = (val===undefined ? 0 : val)
* this.children = []
* }
* }
*/
function levelOrder(root: Node | null): number[][] {
let res: number[][] = [];
let tmpList: Node[] = [];
if(root !== null) tmpList.push(root);
while(tmpList.length > 0){
let tmpArray: number[] = [];
for(let i = 0, length = tmpList.length; i < length; i++){
let tmpNode: Node = tmpList.shift()!;
tmpArray.push(tmpNode.val);
if(tmpNode.children !== null){
for(let i = 0; i < tmpNode.children.length; i++){
tmpList.push(tmpNode.children[i]);
}
}
}
res.push(tmpArray);
}
return res;
};
力扣链接:https://leetcode.cn/problems/symmetric-tree/
给你一个二叉树的根节点 root , 检查它是否轴对称。
示例1是轴对称的,示例2是非轴对称的:
三种方法,一种是递归判断,不断的判断1.左子树的左节点和右子树的右节点是否相同;2.左子树的右节点和右子树的左节点是否相同。另一种是层序遍历法,判断每一层的节点是否满足轴对称,第三种是迭代法,使用队列来判断根节点的左子树和右子树的内侧和外侧是否相等。
//递归法
function isSymmetric(root: TreeNode | null): boolean {
if(root === null)
return true;
return recur(root.left, root.right);
function recur(left: TreeNode | null, right: TreeNode | null): boolean{
if(left === null && right === null)
return true;
if(left === null || right === null)
return false;
if(left.val != right.val)
return false;
let isSym1: boolean = recur(left.left, right.right);
let isSym2: boolean = recur(left.right, right.left);
return isSym1&&isSym2;
}
};
//迭代法
function isSymmetric(root: TreeNode | null): boolean {
let helpQue: (TreeNode | null)[] = [];
let tmpNode1: TreeNode | null;
let tmpNode2: TreeNode | null;
if(root !== null){
helpQue.push(root.left);
helpQue.push(root.right);
}
while(helpQue.length > 0){
tmpNode1 = helpQue.shift();
tmpNode2 = helpQue.shift();
if(tmpNode1 === null && tmpNode2 === null)
continue;
if(tmpNode1 === null || tmpNode2 === null)
return false;
if(tmpNode1.val !== tmpNode2.val)
return false;
helpQue.push(tmpNode1.left);
helpQue.push(tmpNode2.right);
helpQue.push(tmpNode1.right);
helpQue.push(tmpNode2.left);
}
return true;
};
力扣链接:https://leetcode.cn/problems/maximum-depth-of-binary-tree/
给定一个二叉树
,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数
。
递归法,使用Math.max()来得到左子树和右子树的最大深度
function maxDepth(root: TreeNode | null): number {
if(root == null) return 0;
function treeLength(root: TreeNode): number{
if(root === null)
return 0;
else
return 1 + Math.max(treeLength(root.left),treeLength(root.right));
}
return treeLength(root);;
};
迭代法,使用层序遍历,找到有多少层(直接改层序遍历的代码就可以了)。
给定一个二叉树
,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量
。
说明:叶子节点
是指没有子节点的节点。
这题看起来和求最大深度类似,但是并不能直接套用把max改成min,此题要注意叶子节点指没有子节点的节点,左子树为空时并不能说左边就是最小的深度,而且要找到叶子节点才能算深度。
此题可以使用1.遍历法,找到所有的叶子结点,然后算各个叶子结点的深度,然后记录最小值;2.递归法,找到左子树和右子树的深度,计算最小深度(但是要注意递归条件),这里给出递归法的写法:
function minDepth(root: TreeNode | null): number {
if(root == null) return 0;
function treeLength(root: TreeNode): number{
if(root.left === null && root.right === null)
return 1;
else{
if(root.left === null)
return 1 + treeLength(root.right);
else if(root.right === null)
return 1 + treeLength(root.left);
else
return 1 + Math.min(treeLength(root.left),treeLength(root.right));
}
}
return treeLength(root);;
};
力扣链接:https://leetcode.cn/problems/count-complete-tree-nodes
给你一棵 完全二叉树
的根节点 root ,求出该树的节点个数。
完全二叉树
的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2 h 2^h 2h 个节点。
本题可以使用遍历法(递归或者迭代)来做,下面给出使用队列进行层序遍历:
function countNodes(root: TreeNode | null): number {
let res: number = 0;
if(root == null) return res;
let myQue: TreeNode[] = [];
myQue.push(root);
while(myQue.length > 0){
let tmpNode: TreeNode = myQue.shift()!;
res++;
if(tmpNode.left != null)
myQue.push(tmpNode.left);
if(tmpNode.right != null)
myQue.push(tmpNode.right);
}
return res;
};
而对于完全二叉树,可以利用其特性来做,如果整个树不是满二叉树,就递归其左右孩子,直到遇到满二叉树为止,用公式计算这个子树(满二叉树)的节点数量。
function countNodes(root: TreeNode | null): number {
if(root == null)
return 0;
let left: number = 0;
let right: number = 0;
let curNode: TreeNode | null = root;
while(curNode !== null){
left ++;
curNode = curNode.left;
}
curNode = root;
while(curNode !== null){
right ++;
curNode = curNode.right;
}
if(left == right)
return 2 **left - 1;
return 1 + countNodes(root.left) + countNodes(root.right);
};
题目链接:https://leetcode.cn/problems/balanced-binary-tree/
给定一个二叉树,判断它是否是高度平衡的二叉树。
使用递归的方法解决,与求最大深度的方法类似,但是在计算左子树和右子树高度时判断其是否差是否大于1,如果大于1则已经出现不平衡了返回-1。
function isBalanced(root: TreeNode | null): boolean {
let res: boolean = true;
if(root == null) return true;
else return treeLength(root) > 0;
function treeLength(root: TreeNode | null): number{
if(root == null)
return 0;
let left = treeLength(root.left);
let right = treeLength(root.right);
if(left >= 0 && right >= 0 && Math.abs(left - right) <= 1){
return Math.max(left, right) + 1;
}else{
return -1; //已经不平衡了
}
}
};
力扣链接:https://leetcode.cn/problems/binary-tree-paths/
给你一个二叉树的根节点 root ,按 任意顺序
,返回所有从根节点到叶子节点的路径。
此题使用遍历的思想,遍历的途中使用栈的结构,保存路径点,而当遍历到叶子节点时,将路径存到res返回结果中。
此题可以采用递归,其可以直接达到栈的作用,递归遍历方法为先序遍历:
function binaryTreePaths(root: TreeNode | null): string[] {
let res: string[] = [];
if(root === null) return null;
traverse(root, '', res);
return res;
function traverse(node: TreeNode, route:string, res: string[]):void{
route = route + node.val.toString();
if(node.left === null && node.right === null){
res.push(route);
return;
}
else{
if(node.left != null)
traverse(node.left, route + '->', res);
if(node.right != null)
traverse(node.right, route + '->', res);
}
}
};
力扣链接:https://leetcode.cn/problems/construct-binary-tree-from-inorder-and-postorder-traversal/
给定两个整数数组 inorder
和 postorder
,其中 inorder
是二叉树的中序遍历, postorder
是同一棵树的后序遍历,请你构造并返回这颗 二叉树
。
由后序可知,根结点一定在最后面,以 后序数组的最后一个元素
为切割点,先切中序数组,根据中序数组,反过来在切后序数组。一层一层切下去,每次后序数组最后一个元素就是节点元素。
切割我们使用slice
,slice()
方法可从已有的数组中返回选定的元素,但其不会改变原始数组。只传一个参数就是从参数位开始截取,一直到字符串末尾,传两个参数就是从第一个参数start到第二个参数end位截取。
这种一层一层的切割,可以使用递归方法实现:
function buildTree(inorder: number[], postorder: number[]): TreeNode | null {
if(inorder.length === 0) return null;
let rootVal: number = postorder.pop();
let rootIndex: number = inorder.indexOf(rootVal);
const rootNode: TreeNode = new TreeNode(rootVal);
rootNode.left = buildTree(inorder.slice(0, rootIndex), postorder.slice(0, rootIndex));
rootNode.right = buildTree(inorder.slice(rootIndex + 1), postorder.slice(rootIndex));
return rootNode;
};
力扣链接:https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/
方法相同,唯一不同的是由前序可得根结点在最前面,相应题解为:
function buildTree(preorder: number[], inorder: number[]): TreeNode | null {
if(preorder.length === 0) return null;
let rootVal: number = preorder.shift();
let rootIndex: number = inorder.indexOf(rootVal);
const rootNode: TreeNode = new TreeNode(rootVal);
rootNode.left = buildTree(preorder.slice(0, rootIndex), inorder.slice(0, rootIndex));
rootNode.right = buildTree(preorder.slice(rootIndex), inorder.slice(rootIndex + 1));
return rootNode;
};
力扣链接:https://leetcode.cn/problems/maximum-binary-tree
给定一个不重复的整数数组 nums
。 最大二叉树 可以用下面的算法从 nums
递归地构建:
创建一个根节点,其值为 nums
中的最大值。
递归地在最大值 左边 的 子数组前缀上 构建左子树。
递归地在最大值 右边 的 子数组后缀上 构建右子树。
返回 nums
构建的 最大二叉树 。
和3.7类似,只不过找根结点现在是找数组中的最大值点
function constructMaximumBinaryTree(nums: number[]): TreeNode | null {
if(nums.length === 0)
return null;
let tmpMax: number = nums[0];
for(let i:number = 0; i < nums.length; i++){
if(nums[i] > tmpMax)
tmpMax = nums[i];
}
let resIndex: number = nums.indexOf(tmpMax);
const resRoot: TreeNode = new TreeNode(tmpMax);
resRoot.left = constructMaximumBinaryTree(nums.slice(0, resIndex));
resRoot.right = constructMaximumBinaryTree(nums.slice(resIndex+1));
return resRoot;
};
力扣链接:https://leetcode.cn/problems/merge-two-binary-trees
给你两棵二叉树: root1
和 root2
。想象一下,当你将其中一棵覆盖到另一棵之上时,两棵树上的一些节点将会重叠(而另一些不会)。你需要将这两棵树合并成一棵新二叉树。合并的规则是:如果两个节点重叠,那么将这两个节点的值相加作为合并后节点的新值;否则,不为 null
的节点将直接作为新二叉树的节点。返回合并后的二叉树。
同样使用递归的方法:
function mergeTrees(root1: TreeNode | null, root2: TreeNode | null): TreeNode | null {
if(root1 === null)
return root2;
if(root2 === null)
return root1;
let resRoot: TreeNode = new TreeNode(root1?.val + root2?.val);
resRoot.left = mergeTrees(root1.left, root2.left);
resRoot.right = mergeTrees(root1.right, root2.right);
return resRoot;
};
个人简介:人工智能领域研究生,目前主攻文本生成图像(text to image)方向
关注我:中杯可乐多加冰
限时免费订阅:TypeScript实战
加入社群 抱团学习:中杯可乐的答疑交流群
支持我:点赞+收藏⭐️+留言