【路飞】二叉树最大宽度

158 阅读4分钟

记录 1 道算法题

二叉树的最大宽度

662. 二叉树最大宽度 - 力扣(LeetCode) (leetcode-cn.com)


这道题有一定难度。

每一层最左边的非空节点和最右边的非空节点之间的个数就是每一层的宽度。然后最大宽度是所有层中最大的那个个数。最左边的非空节点和最右边的非空节点中间的空节点也算个数。

即 [1, 2, 3, null , 5] 如果这一层的节点是这样的,那么宽度是 5.

如果 [null, 2, 3 , null] 这一层的宽度是 2.

使用层序遍历可以收集到每一层的节点,但是考虑到中间会有空节点,所以还是用下标比较合理。每个节点都进行标记,然后 像数组的下标一样的规律 count = endIdx - startIdx + 1

            a 1
       b 2        c 3
  d 4     e 5    6    f 7

像上面标记的第一个节点是1 ,刚好符合规律 左子节点是父节点的下标*2,右子节点就是左子节点+1,即 2*父节点下标+1。

为了保存节点和下标,我们使用对象进行包装之后再推入数组,根节点就是 { node: root, i: 1 }

    function widthOfBinaryTree(root) {
        const stack = [{ node: root, i: i }]
        let width = 1
        
        while(stack.length) {
            // stack代表某一层的节点,所以在开头计算的是当前的层
            // 如果放在了结尾就是计算下一层的节点
            width = Math.max(width, stack[stack.length - 1].i - stack[0].i + 1)
            for(let j = stack.length; j > 0; j--) {
                // 拿出每一个父节点,并收集子节点,计算下标
                const { node, i } = stack.shift()
                node.left && stack.push({ node: node.left, i: 2*i })
                node.right && stack.push({ node: node.right, i: 2*i+1 })
            }
            
            /* 在这算 width 也行, 在这计算就要判断 stack 是不是空数组 */
        }
        
        return width
    }

上面就可以计算出普通的二叉树了。但是在 leetcode 测试用例第 109 个的时候,就返回了NaN。

找了很久发现是因为当二叉树的规模超级大的时候,下标超出了最大数字,变成了 Infinity。然后在上面比较 Math.max(width, x) 的时候, 当stack里面只有一个节点的时候,出现了 Infinity - Infinity, 结果自然是 NaN。

其实计算下标也只是利用 后 - 前 + 1 的规律计算个数而已 4 - 2 + 1 和 14 - 12 + 1 是没有区别的,都能算出个数是3。所以可以考虑在某些条件下将下标重置。

当 stack 只有一个节点的时候, 比较 Math.max(width, x) 是没有意义的。所以可以在遇到只有一个节点的时候, 选择跳过,将下标重置为 1,就是把这个节点当成根节点开始。如果它只有一个子节点,那毫无疑问又会在下一轮出现 stack 只有一个节点的情况,所以这时候就是直接把子节点当作根节点开始。

    if (stack.length === 1) {
        const { node } = stack[0]
        // 只有一个节点,而且是叶子节点的时候,说明已经遍历完了
        if (!node.left && !node.right) break
        // 只有其中一个节点的时候
        if (!node.left || !node.right) {
            const _ = stack[0]
            // 只有一个子节点的时候,往下找一层
            _.node = node.left ?? node.right
            _.i = 1
            continue
        }
        // 如果两个节点都有,就作为根继续下去
    }

这样可以排查掉一部分不需要计算的。但是当二叉树规模足够大的时候,也还是会超出最大数字。

完整代码如下:

    function widthOfBinaryTree(root) {
        const stack = [{ node: root, i: i }]
        let width = 1
        
        while(stack.length) {
             if (stack.length === 1) {
                const { node } = stack[0]
                // 只有一个节点,而且是叶子节点的时候,说明已经遍历完了
                if (!node.left && !node.right) break
                // 只有其中一个节点的时候
                if (!node.left || !node.right) {
                    const _ = stack[0]
                    // 只有一个子节点的时候,往下找一层
                    _.node = node.left ?? node.right
                    _.i = 1
                    continue
                }
                // 如果两个节点都有,就作为根继续下去
            }
            // stack代表某一层的节点,所以在开头计算的是当前的层
            // 如果放在了结尾就是计算下一层的节点
            width = Math.max(width, stack[stack.length - 1].i - stack[0].i + 1)
            for(let j = stack.length; j > 0; j--) {
                // 拿出每一个父节点,并收集子节点,计算下标
                const { node, i } = stack.shift()
                node.left && stack.push({ node: node.left, i: 2*i })
                node.right && stack.push({ node: node.right, i: 2*i+1 })
            }
            
            /* 在这算 width 也行, 在这计算就要判断 stack 是不是空数组 */
        }
        
        return width
   }

结束