• home > theory > algorithm > Tree2Graph >

    讲透学烂二叉树(六):二叉树的笔试题:翻转|宽度|深度

    Author:zhoulujun Date:

    据说Homebrew作者去谷歌涮二叉树翻转被KO,害的我再次把二叉树常用的算法整理收集下,万一哪天被fire了。当然学习并不是为了面试啥的

    翻转|镜像二叉树

    华为面试题——将二叉树的两个孩子换位置,即左变右,右变左。


    941251071-5be058796bcaa_articlex.png

    90% of our engineers use the software you wrote (Homebrew), but you can’t invert a binary tree on a whiteboard so f* off.

    递归实现

    先序遍历每个结点,翻转每个结点。

    下面来分析具体的实现思路:

    • 对于根结点为空的情况

      这种情况需要排除,因为null不是一个对象,不可能存在左右子树并且可以翻转的情况

    • 对根不为空的情况,翻转该节点


    JavaScript代码实现二叉树翻转

    reverseTree (root = this.tree) {
        if (root &&root.data) {
            [root.leftChild, root.rightChild] = [root.rightChild, root.leftChild]
            this.reverseTree(root.leftChild)
            this.reverseTree(root.rightChild)
        }
    }

    循环,栈存储(DFS,非递归)

    本质思想是,左右节点进行交换,循环翻转每个节点的左右子节点,将未翻转的子节点存入栈中,循环直到栈里所有节点都循环交换完为止。

    reverseTree3 (node) {
        if (!node) {
            return 0
        }
        let queue = [node]
        while (queue.length) {
            let temp = queue.shift();
            [temp.leftChild, temp.rightChild] = [temp.rightChild, temp.leftChild]
            temp.leftChild && queue.push(temp.leftChild)
            temp.rightChild && queue.push(temp.rightChild)
        }
        return node
    }

    层序遍历,翻转每层的结点

    JavaScript代码实现

    reverseTree2 (node = this.tree) {
        let buckets = [[node]]
        let i = 0
    
        function getChildNode (root, i) {
            if (!root || !root.data) {
                return false
            }
            i++
            if (buckets[i] === undefined) {
                buckets[i] = []
            }
            if (root.leftChild) {
                buckets[i].push(root.leftChild)
                getChildNode(root.leftChild, i)
            }
            if (root.rightChild) {
                buckets[i].push(root.rightChild)
                getChildNode(root.rightChild, i)
            }
        }
        getChildNode(node, i)
        for (let i = 0; i < buckets.length; i++) {
            for(let j=0;j<buckets[i].length;j++){
                if(i>1){
                    let parentIndex = buckets[i-1].length-1-Math.floor(i/2)
                    buckets[i][j]['parent']=buckets[i-1][parentIndex]
                }
                buckets[i+1].reverse()
                let leftChildIndex = i*2
                let rightChildIndex = i*2+1
                if(buckets[i+1][leftChildIndex]){
                    buckets[i][j]['leftChild']=buckets[i+1][leftChildIndex]
                }
                if(buckets[i+1][rightChildIndex]){
                    buckets[i][j]['rightChild']=buckets[i+1][rightChildIndex]
                }
                if(i===buckets.length-1){
                    break
                }
    
            }
    
        }
        return node
    }

    就是翻转每一层的数据

    求二叉树的深度

    分析过程

    1. 只有一个根结点时,二叉树深度为1

    2. 只有左子树时,二叉树深度为左子树深度加1

    3. 只有右子树时,二叉树深度为右子树深度加1

    4. 同时存在左右子树时,二叉树深度为左右子树中深度最大者加1

    JavaScript求二叉树深度,代码实现

    getTreeDepth(node){
        let leftD =1
        let rightD =1
        function getDeep(node){
            if(!node||!node.data){
                return 0
            }
            if(node.leftChild){
                leftD++
                getDeep(node.leftChild)
            }
            if(node.rightChild){
               rightD++
               getDeep(node.rightChild)
            }
        }
        return leftD>rightD?leftD:rightD
    }


    求二叉树的宽度

    二叉树的宽度是啥?我把它理解为具有最多结点数的层中包含的结点数

    分析过程

    根据上图,我们如何算出二叉树的宽度呢?其实有个很简单的思路:

    1. 算出第一层的结点数,保存

    2. 算出第二层的结点数,保存一二层中较大的结点数

    3. 重复以上过程

    getTreeWidth (node) {
        let queue = [node]
        let max = 1
        let width = queue.length
        while (queue.length) {
            width=queue.length
            while (width) {
                let temp = queue.shift()
                if (temp.leftChild) {
                    queue.push(temp.leftChild)
                }
                if (temp.rightChild) {
                    queue.push(temp.rightChild)
                }
                width--
            }
            max = queue.length > max ? queue.length : max
        }
        return max
    }


    判断一颗二叉树是否为平衡二叉树

    解决思路一:按照前序遍历的路线判断。

    1. 判断以根结点的树是否为二叉平衡树。求出左右子树的高度,判断它们的高度差是否超过了1。

    2. 递归判断根的左子树是否为平衡二叉树

    3. 递归判断根的右子树是否为平衡二叉树

    解决思路二:按照后序遍历的路线判断

    • 首先,判断它的左子树是否为平衡二叉树

    • 然后在判断它的右子树是否为平衡二叉树

    • 判断它们是否为平衡二叉树的同时,记录它们的左右子树的深度

    • 最后在判断以这个结点为根的树是否为平衡二叉树


    在排序数组中查找元素的第一个和最后一个位置(中等)

    给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。

    你的算法时间复杂度必须是 O(log n) 级别。

    如果数组中不存在目标值,返回 [-1, -1]。

    示例 1:

    输入: nums = [5,7,7,8,8,10], target = 8 输出: [3,4] 示例 2:

    输入: nums = [5,7,7,8,8,10], target = 6 输出: [-1,-1]

    二分查找分析与思路

    按照正常的二分查找思路,循环找到匹配目标数字的下标,继续将右指针变小查找到第一个目标数字。用新的循环从找到的第一个目标数字下标从左往右查找直到最后一个目标数字。(具体的查找思路见伪代码)

    /**
     * 二分查找数组中,第一出线目标数据的前后位置, 如果没有返回[-1,-1]
     * @param arr {Array}
     * @param target {Number}
     * @return {number[]}
     */
    function searchRange (arr, target) {
        // 声明搜索用的左右指针,初始左指针下标0,右指针下标数组末位nums.length-1;
        let l = 0, r = arr.length - 1
        // 获取数组中间下标pivot = (l + r) / 2;
        let pivot = Math.floor((l + r) / 2)
        // 声明创建结果数组,初始化赋值-1;
        let res = [-1, -1]
        // 循环二分查找,直到左指针大于右指针查找结束
        while (l <= r) {
            // 若中间位置数字小于目标数字,说明目标数字在pivot右边,将左指针l右移赋值为pivot+1;
            if (arr[pivot] < target) {
                l = pivot + 1
                // 若中间位置数字大于目标数字,说明目标数字在pivot左边,将右指针r左移赋值为pivot-1;
            } else if (arr[pivot] > target) {
                r = pivot - 1
                // 若中间位置数字等于目标数字:
            } else {
                // 将pivot作为第一次出现位置存入数组;
                res[0] = pivot
                // 右指针r左移赋值为pivot-1,继续查找相等的数字直到找到第一次出现的位置;
                r = pivot - 1
            }
            // 更新pivot;
            pivot = (l + r) / 2
        }
        // 从第一次出现的位置开始,循环往右查找最后一次出现的位置:
        // 声明pr指针初始赋值为第一次出现位置下标;
        let pr = res[0]
        // 当二分查找已找到目标数字时
        if (pr !== -1) {
            // 循环直到数组越界或者数组当前元素不等于目标元素时结束:
            while (pr <= arr.length - 1 && target === arr[pr]) {
                // 更新最后一次出现位置下标;
                pr += 1
                // 更新最后一次出现位置下标;
                res[1] = pr
            }
        }
        return res
    }
    console.log(searchRange([-1,5,5,6,8,9,13,22],-2))


    参考文章:

    使用JavaScript完成二叉树的一些基本操作 https://segmentfault.com/a/1190000016914803

    JavaScript二叉树实现和原理完全讲解 www.srcmini.com/1772.html

    LeetCode进阶226-翻转二叉树(华为面试题) https://zhuanlan.zhihu.com/p/76818774

    面试BAT 却被二叉树秒杀?20 道题帮你一举拿下二叉树算法题 https://zhuanlan.zhihu.com/p/88361872





    转载本站文章《讲透学烂二叉树(六):二叉树的笔试题:翻转|宽度|深度》,
    请注明出处:https://www.zhoulujun.cn/html/theory/algorithm/TreeGraph/8289.html