662. Maximum Width of Binary Tree

题干

662. Maximum Width of Binary Tree
Difficulty: Medium
Given a binary tree, write a function to get the maximum width of the given tree. The width of a tree is the maximum width among all levels. The binary tree has the same structure as a full binary tree, but some nodes are null.

The width of one level is defined as the length between the end-nodes (the leftmost and right most non-null nodes in the level, where the null nodes between the end-nodes are also counted into the length calculation.

给予一个二叉树,得到这个树的最大宽度。
最大宽度指的是二叉树的同一层中,最左边的非空节点和最右边的非空节点之间的距离。

解题思路

如同题干中分析的,只需要找到每一层最左边的节点和最右边的节点就可以了,中间所有的节点我们都不care。
不过说来惭愧,这道中等难度的题,解了两个小时,写了三种写法,其中第一种是错的,第两种都谈不上最优解,而如果一个认真学数据结构的人来做这题的话,可能第一次就能写出最优解了。

第一种错误写法:
        var widthOfBinaryTree = function (root) {
            let left = [];
            let right = [];
            if (!root) {
                return 0
            }
            const traverse = (root) => {
                if (root.left) {
                    left.push(0);
                    traverse(root.left);
                } else if (root.right) {
                    left.push(1);
                    traverse(root.right)
                }
            };
            const inversiveTraverse = (root) => {
                if (root.right) {
                    right.push(0);
                    inversiveTraverse(root.right);
                } else if (root.left) {
                    right.push(1);
                    inversiveTraverse(root.left)
                }
            };
            traverse(root);
            inversiveTraverse(root);
            left.map((x, i, arr) => {
                if (i) {
                    return arr[i - 1] * 2 + x;
                } else {
                    return x;
                }
            });
            right.map((x, i, arr) => {
                if (i) {
                    return arr[i - 1] * 2 + x;
                } else {
                    return x;
                }
            });
            let max = -1;
            left.forEach((x, i) => {
                let tmp = (2 ** (i + 1) - left[i] - right[i]);
                if (tmp > max) {
                    max = tmp;
                }
            })
            return max;
        };

其实思路上没有太大的问题,遍历每个层级上最靠左和最靠右的节点即可,之所以这么想是因为我觉得没有必要遍历所有的节点,尽可能的遍历两边的节点就可以了,然后通过记录走过的路径来计算出最靠边的节点的位置,进而计算出最大的宽度,然而这个写法的问题在于我没法处理某个不在最后一层的叶子节点,进而我想到了一个改进写法,DP。

动态规划:

如果在遍历的时候走到了某个不处于最后一层的叶子节点,为了遍历到更深的层级,我需要回溯到父节点,然而一般二叉树是不会有父节点指针的,所以我需要额外维护两个队列,记录遍历时走过的路径,以及走过的方向。

        var widthOfBinaryTreeDP = function (root) {
            if (!root) {
                return 0;
            }
            // 计算所处的位置
            const calcPos = (arr) => {
                let sum = 0;
                arr.forEach((x) => {
                    sum = sum * 2 + x;
                });
                return sum;
            }
            let left = []; // 记录每一层最左边的节点
            let leftNode = []; // 遍历时的节点路径队列
            let leftDirection = []; // 遍历时的路径方向队列
            const leftDp = (root) => {
                if (!root) return;
                if (root.left) {
                    // 记录路径的节点和方向
                    leftNode.push(root);
                    leftDirection.push(0);
                    // 第一个找到的节点必定是最远的那个,记录距离
                    if (left[leftNode.length - 1] === undefined) {
                        left.push(calcPos(leftDirection));
                    }
                    leftDp(root.left);
                } else if (root.right) {
                    leftNode.push(root);
                    leftDirection.push(1);
                    if (left[leftNode.length - 1] === undefined) {
                        left.push(calcPos(leftDirection));
                    }
                    leftDp(root.right);
                } else {
                    // 如果找到叶子节点,则开始回溯
                    let node = leftNode.pop();
                    let direction = leftDirection.pop();
                    // 回溯到第一个存在右节点且在上次遍历中走向了左节点的节点
                    while ((direction === 1 || (direction === 0 && !node.right)) && leftNode.length) {
                        node = leftNode.pop();
                        direction = leftDirection.pop();
                    }
                    // 回溯成功,改变路径方向,改为走向右节点
                    if (leftNode.length || direction === 0) {
                        leftNode.push(node);
                        leftDirection.push(1);
                        if (left[leftNode.length - 1] === undefined) {
                            left.push(calcPos(leftDirection));
                        }
                        leftDp(node.right);
                    } else {
                        // 根节点再上一次遍历中已经走向了右节点,则说明已经遍历所有可能性
                        return;
                    }
                }
            }

            // 对最右节点的遍历与上面类似,只是优先找最右节点。
            let right = [];
            let rightNode = [];
            let rightDirection = [];
            const rightDp = (root) => {
                if (!root) return;
                if (root.right) {
                    rightNode.push(root);
                    rightDirection.push(0);
                    if (right[rightNode.length - 1] === undefined) {
                        right.push(calcPos(rightDirection));
                    }
                    rightDp(root.right);
                } else if (root.left) {
                    rightNode.push(root);
                    rightDirection.push(1);
                    if (right[rightNode.length - 1] === undefined) {
                        right.push(calcPos(rightDirection));
                    }
                    rightDp(root.left);
                } else {
                    let node = rightNode.pop();
                    let direction = rightDirection.pop();
                    while ((direction === 1 || (direction === 0 && !node.left)) && rightNode.length) {
                        node = rightNode.pop();
                        direction = rightDirection.pop();
                    }
                    if (rightNode.length || direction === 0) {
                        rightNode.push(node);
                        rightDirection.push(1);
                        if (right[rightNode.length - 1] === undefined) {
                            right.push(calcPos(rightDirection));
                        }
                        rightDp(node.left);
                    } else {
                        return;
                    }
                }
            }
            leftDp(root);
            rightDp(root);
            let max = 1;
            // 计算最大宽度
            left.forEach((x, i) => {
                let tmp = (2 ** (i + 1) - left[i] - right[i]);
                if (tmp > max) {
                    max = tmp;
                }
            })
            return max;
        };

成功AC,本来写到这就结束了,然而却看到一句Your runtime beats 5.83% of javascript submission。
这也太丢脸了,这算法我调了整整一个小时啊,不行我得写个好看的。

深度优先遍历

一开始要我用遍历的时候,我是拒绝的,毕竟我只需要最靠边的节点就可以了,没有必要去遍历那些中间的节点,但是由于我一开始并不知道深度,所以我在做DP的时候其实也把大半的节点给遍历了,但是DP我需要做两次,第一次找最左,第二次找最右,于是乎总体的时间是大于做一次遍历的。
如果我知道树的深度,那么用DP则是更好的一个解法,可惜求解树的深度的算法本身就是一个BFS,所以这题的解法最优解实际上是BFS和DFS,我自己写了一个BFS版本的。

        var widthOfBinaryTreeDFS = function (root) {
            let ranges = [];
            let max = 0;
            const traverse = (root, depth, pos) => {
                if (!root) {
                    return;
                }
                if (root.left) {
                    left = traverse(root.left, depth + 1, pos * 2);
                }
                if (root.right) {
                    right = traverse(root.right, depth + 1, pos * 2 + 1);
                }
                if (ranges[depth]) {
                    if (pos <= ranges[depth][1] && pos > ranges[depth][1]) {
                        return;
                    } else {
                        if (pos > ranges[depth][1]) {
                            ranges[depth][1] = pos;
                        } else if (pos < ranges[depth][0]) {
                            ranges[depth][0] = pos;
                        }
                        let range = ranges[depth][1] - ranges[depth][0] + 1;
                        if (max < range) {
                            max = range;
                        }
                    }
                } else {
                    ranges[depth] = [pos, pos];
                    if (max === 0) {
                        max = 1;
                    }
                }
            }
            traverse(root, 0, 0);
            return max;
        };

记录下每个节点的深度和位置,再判断该节点是否比同深度其他节点要更加靠近边界,若是则更新当前深度的宽度,并和最大宽度做比较,来更新最大宽度。
写的简单,算的还快。做了一点小优化以后得出了上面的算法,成功AC,得到一句Your runtime beats 98.06 % of javascript submissions。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,509评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,806评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,875评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,441评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,488评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,365评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,190评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,062评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,500评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,706评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,834评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,559评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,167评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,779评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,912评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,958评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,779评论 2 354