TypeScript算法题实战——二叉树篇

二叉树是树形结构的一个重要类型。二叉树是指树中节点的度不大于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;
    }
}

二、二叉树的遍历

二叉树的遍历方式主要有:先序遍历、中序遍历、后序遍历、层次遍历。先序、中序、后序其实指的是父节点被访问的次序。若在遍历过程中,父节点先于它的子节点被访问,就是先序遍历;父节点被访问的次序位于左右孩子节点之间,就是中序遍历;访问完左右孩子节点之后再访问父节点,就是后序遍历。

2.1、采用递归实现遍历

很简单,设计一个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;
};

2.2、采用迭代实现遍历

使用到栈的结构
先序遍历,根结点入栈,出栈,然后右子树入栈,左子树入栈出栈

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;
};

2.3、层序遍历

由上到下,力扣链接: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;	
};

三、二叉树的进阶

3.1、对称二叉树

力扣链接:https://leetcode.cn/problems/symmetric-tree/
给你一个二叉树的根节点 root , 检查它是否轴对称。

示例1是轴对称的,示例2是非轴对称的:
TypeScript算法题实战——二叉树篇_第1张图片
TypeScript算法题实战——二叉树篇_第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;
};

3.2、二叉树的最大深度

3.2.1、题目描述

力扣链接:https://leetcode.cn/problems/maximum-depth-of-binary-tree/
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数

3.2.2、题解

递归法,使用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);;
};

迭代法,使用层序遍历,找到有多少层(直接改层序遍历的代码就可以了)。

3.3、二叉树的最小深度

3.3.1、题目描述

给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量
说明:叶子节点是指没有子节点的节点。

3.3.2、题解

这题看起来和求最大深度类似,但是并不能直接套用把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);;
};

3.4、完全二叉树的节点个数

3.4.1、题目描述

力扣链接:https://leetcode.cn/problems/count-complete-tree-nodes
给你一棵 完全二叉树 的根节点 root ,求出该树的节点个数。

完全二叉树 的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2 h 2^h 2h 个节点。

3.4.2、题解

本题可以使用遍历法(递归或者迭代)来做,下面给出使用队列进行层序遍历:

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);
};

3.5、平衡二叉树

3.5.1、题目描述

题目链接:https://leetcode.cn/problems/balanced-binary-tree/
给定一个二叉树,判断它是否是高度平衡的二叉树。

3.5.2、题解

使用递归的方法解决,与求最大深度的方法类似,但是在计算左子树和右子树高度时判断其是否差是否大于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; //已经不平衡了
        }
    }
};

3.6、二叉树的所有路径

3.6.1、题目描述

力扣链接:https://leetcode.cn/problems/binary-tree-paths/
给你一个二叉树的根节点 root ,按 任意顺序 ,返回所有从根节点到叶子节点的路径。
TypeScript算法题实战——二叉树篇_第3张图片

3.6.2、题解

此题使用遍历的思想,遍历的途中使用栈的结构,保存路径点,而当遍历到叶子节点时,将路径存到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);
        }
    }
};

3.7、由中序后序求前序二叉树

3.7.1、题目描述

力扣链接:https://leetcode.cn/problems/construct-binary-tree-from-inorder-and-postorder-traversal/
给定两个整数数组 inorderpostorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树

3.7.2、题解

由后序可知,根结点一定在最后面,以 后序数组的最后一个元素为切割点,先切中序数组,根据中序数组,反过来在切后序数组。一层一层切下去,每次后序数组最后一个元素就是节点元素。
切割我们使用sliceslice() 方法可从已有的数组中返回选定的元素,但其不会改变原始数组。只传一个参数就是从参数位开始截取,一直到字符串末尾,传两个参数就是从第一个参数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;
};

3.7.3、相似题:从前序与中序遍历序列构造二叉树

力扣链接: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;
};

3.8、最大二叉树

3.8.1、题目描述

力扣链接:https://leetcode.cn/problems/maximum-binary-tree
给定一个不重复的整数数组 nums最大二叉树 可以用下面的算法从 nums 递归地构建:

创建一个根节点,其值为 nums 中的最大值。
递归地在最大值 左边 的 子数组前缀上 构建左子树。
递归地在最大值 右边 的 子数组后缀上 构建右子树。
返回 nums 构建的 最大二叉树

3.8.2、题解

和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;
};

3.9、合并二叉树

3.9.1、题目描述

力扣链接:https://leetcode.cn/problems/merge-two-binary-trees
给你两棵二叉树root1root2 。想象一下,当你将其中一棵覆盖到另一棵之上时,两棵树上的一些节点将会重叠(而另一些不会)。你需要将这两棵树合并成一棵新二叉树。合并的规则是:如果两个节点重叠,那么将这两个节点的值相加作为合并后节点的新值;否则,不为 null 的节点将直接作为新二叉树的节点。返回合并后的二叉树。

TypeScript算法题实战——二叉树篇_第4张图片

3.9.2、题解

同样使用递归的方法:

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实战

加入社群 抱团学习:中杯可乐的答疑交流群

支持我:点赞+收藏⭐️+留言

你可能感兴趣的:(TypeScript实战,typescript,算法,面试,二叉树,数据结构)