[Leetcode Mathematics Weekly] Issue 6

Posted by bridawg on Sun, 15 Dec 2019 10:05:46 +0100

First published in WeChat's Public Number Front End Growth, written on December 15, 2019

background

This paper records the whole process of thinking during the brushing process for reference.The main content covers:

  • Assumptions for Topic Analysis
  • Write code validation
  • Look up other people's solutions
  • Thinking Summary

Catalog

Easy

110. Balanced Binary Tree

Title Address

Title Description

Given a binary tree, determine whether it is a highly balanced binary tree.

In this topic, a height-balanced binary tree is defined as:

The absolute difference between the left and right subtrees of each node of a binary tree is no more than 1.

Example 1:

Given a binary tree [3,9,20,null,null,15,7]

    3
   / \
  9  20
    /  \
   15   7

Returns true.

Example 2:

Given Binary Tree [1,2,2,3,3,null,null,4,4]

       1
      / \
     2   2
    / \
   3   3
  / \
 4   4

Returns false.

Assumptions for Topic Analysis

One of the toughest ways to solve this problem is to calculate the maximum depth of each subtree to make a height judgment, which is obviously inefficient.We can jump out of the calculation by changing to a bottom-up scenario where the intermediate process does not conform.

Write code validation

I. Judging by calculating the maximum depth of a subtree

Code:

/**
 * @param {TreeNode} root
 * @return {boolean}
 */
var isBalanced = function(root) {
    if (root === null) return true
    function maxDepth (node) {
        if (node === null) return 0
        const l = maxDepth(node.left)
        const r = maxDepth(node.right)
        return Math.max(l, r) + 1
    }

    return Math.abs(maxDepth(root.left) - maxDepth(root.right)) <= 1
    && isBalanced(root.left)
    && isBalanced(root.right)
};

Result:

  • 227/227 cases passed (80 ms)
  • Your runtime beats 77.66 % of javascript submissions
  • Your memory usage beats 26.73 % of javascript submissions (37.8 MB)
  • Time Complexity O(n^2)

II. Bottom-up

Code:

/**
 * @param {TreeNode} root
 * @return {boolean}
 */
var isBalanced = function(root) {
    function maxDepth (node) {
        if (node === null) return 0
        const l = maxDepth(node.left)
        if (l === -1) return -1
        const r = maxDepth(node.right)
        if (r === -1) return -1
        return Math.abs(l - r) <= 1 ? Math.max(l, r) + 1 : -1
    }

    return maxDepth(root) !== -1
};

Result:

  • 227/227 cases passed (72 ms)
  • Your runtime beats 95.44 % of javascript submissions
  • Your memory usage beats 50.5 % of javascript submissions (37.5 MB)
  • Time Complexity O(n)

Look up other people's solutions

These two ideas are basically the same, and no solutions with different directions have been found.

Thinking Summary

Obviously, we all use depth traversal to solve the problem. Calculating subtree depth will find that there are many repetitive operations, so try the bottom-up approach, which returns directly in the process of calculating height, also known as Pre-Blocking.So the suggestion for this question is to use a bottom-up approach.

111. Minimum depth of a binary tree

Title Address

Title Description

Given a binary tree, find its minimum depth.

The minimum depth is the number of nodes on the shortest path from the root node to the nearest leaf node.

Description: A leaf node is a node that has no children.

Example:

Given a binary tree [3,9,20,null,null,15,7],

    3
   / \
  9  20
    /  \
   15   7

Returns its minimum depth of 2.

Assumptions for Topic Analysis

This is a clear top-down question. To determine whether a child node of each node exists or does not exist, the path is the shortest path.If so, the minimum values are compared in depth.Overall, you can also answer in several ways that you previously sought the maximum depth.

Write code validation

I. Recursion

Code:

/**
 * @param {TreeNode} root
 * @return {number}
 */
var minDepth = function(root) {
    if (root === null) return 0
    if (root.left === null && root.right === null) return 1
    let res = Infinity
    if(root.left !== null) {
        res = Math.min(minDepth(root.left), res)
    }
    if(root.right !== null) {
        res = Math.min(minDepth(root.right), res)
    }
    return res + 1
};

Result:

  • 41/41 cases passed (76 ms)
  • Your runtime beats 69.08 % of javascript submissions
  • Your memory usage beats 5.55 % of javascript submissions (37.9 MB)
  • Time Complexity O(n)

II. Using stack iteration

Code:

/**
 * @param {TreeNode} root
 * @return {number}
 */
var minDepth = function(root) {
    if (root === null) return 0
    if (root.left === null && root.right === null) return 1
    // Stack
    let s = [{
        node: root,
        dep: 1
    }]
    let dep = Infinity
    while(s.length) {
        // FIFO
        var cur = s.pop()
        if (cur.node !== null) {
            let curDep = cur.dep
            if (cur.node.left === null && cur.node.right === null) {
                dep = Math.min(dep, curDep)
            }
            if (cur.node.left !== null) s.push({node: cur.node.left, dep: curDep + 1})
            if (cur.node.right !== null) s.push({node: cur.node.right, dep: curDep + 1})
        }
    }
    return dep
};

Result:

  • 41/41 cases passed (68 ms)
  • Your runtime beats 93.82 % of javascript submissions
  • Your memory usage beats 75.31 % of javascript submissions (37 MB)
  • Time Complexity O(n)

III. Using Queues

Code:

/**
 * @param {TreeNode} root
 * @return {number}
 */
var minDepth = function(root) {
    if (root === null) return 0
    if (root.left === null && root.right === null) return 1
    // queue
    let s = [{
        node: root,
        dep: 1
    }]
    let dep = 0
    while(s.length) {
        // FIFO
        var cur = s.shift()
        var node = cur.node
        dep = cur.dep
        if (node.left === null && node.right === null) break;
        if (node.left !== null) s.push({node: node.left, dep: dep + 1})
        if (node.right !== null) s.push({node: node.right, dep: dep + 1})
    }
    return dep
};

Result:

  • 41/41 cases passed (76 ms)
  • Your runtime beats 69.08 % of javascript submissions
  • Your memory usage beats 6.79 % of javascript submissions (37.7 MB)
  • Time Complexity O(n)

Look up other people's solutions

Generally speaking, it is divided into depth-first and breadth-first, the most basic of which is recursion and iteration.No novel solutions to binary tree related topics have been found.

Thinking Summary

It is clear that recursion and utilization stack iteration are depth-first and utilization queues are breadth-first.This is a good place to go from top to bottom, as long as the leaf node is found, it is directly the minimum depth, which eliminates many operations.

112. Sum of paths

Title Address

Title Description

Given a binary tree and a target, it is determined whether there is a path from the root node to the leaf node in the tree. The sum of all the node values on this path equals the target and the leaf node.

Description: A leaf node is a node that has no children.

Example:

Given the following binary tree, with target and sum = 22,

              5
             / \
            4   8
           /   / \
          11  13  4
         /  \      \
        7    2      1

Returns true because there is a path 5->4->11->2 from the root node to the leaf node with a target of 22.

Assumptions for Topic Analysis

My idea for this question is that because you want to find leaf nodes, depth preference is more appropriate, so here are the two methods used in the previous section:

  • recursion
  • Using stack iteration

Write code validation

I. Recursion

Code:

/**
 * @param {TreeNode} root
 * @param {number} sum
 * @return {boolean}
 */
var hasPathSum = function(root, sum) {
    if (root === null) return false
    // Remaining required values
    sum -= root.val
    if (root.left === null && root.right === null) {
        return sum === 0
    } else {
        return hasPathSum(root.left, sum) || hasPathSum(root.right, sum)
    }
};

Result:

  • 114/114 cases passed (80 ms)
  • Your runtime beats 62.09 % of javascript submissions
  • Your memory usage beats 56.9 % of javascript submissions (37.1 MB)
  • Time Complexity O(n)

II. Iteration

Code:

/**
 * @param {TreeNode} root
 * @param {number} sum
 * @return {boolean}
 */
var hasPathSum = function(root, sum) {
    if (root === null) return false
    // Stack
    let stack = [{
        node: root,
        remain: sum - root.val
    }]
    while(stack.length) {
        // FIFO
        var cur = stack.pop()
        var node = cur.node
        if (node.left === null && node.right === null && cur.remain === 0) return true
        if (node.left !== null) {
            stack.push({
                node: node.left,
                remain: cur.remain - node.left.val
            })
        }
        if (node.right !== null) {
            stack.push({
                node: node.right,
                remain: cur.remain - node.right.val
            })
        }
    }
    return false
};

Result:

  • 114/114 cases passed (72 ms)
  • Your runtime beats 88.51 % of javascript submissions
  • Your memory usage beats 33.33 % of javascript submissions (37.2 MB)
  • Time Complexity O(n)

Look up other people's solutions

Here's a scenario where we see post-order traversal, where the path length changes from the previous stack to a variable store, but this one doesn't seem to me to be appropriate, and you can Click here for a review .Another option is to use breadth first and use queues to solve. This is a different idea, so it should be added.

I. Using Queues

Code:

/**
 * @param {TreeNode} root
 * @param {number} sum
 * @return {boolean}
 */
var hasPathSum = function(root, sum) {
    if (root === null) return false
    // queue
    let q = [{
        node: root,
        sum: root.val
    }]
    while(q.length) {
        // Number of elements in the current layer
        for(let i = 0; i < q.length; i++) {
            let cur = q.shift()
            let node = cur.node
            if (node.left === null && node.right === null && cur.sum === sum) return true

            if (node.left !== null) {
                q.push({ node: node.left, sum: cur.sum + node.left.val})
            }
            if (node.right !== null) {
                q.push({ node: node.right, sum: cur.sum + node.right.val})
            }
        }
    }
    return false
};

Result:

  • 114/114 cases passed (72 ms)
  • Your runtime beats 88.51 % of javascript submissions
  • Your memory usage beats 56.32 % of javascript submissions (37.1 MB)
  • Time Complexity O(n)

118. Yang Hui Triangle

Title Address

Title Description

Given a non-negative integer numRows, the first numRows row of Yang Hui triangle is generated.

In Yang Hui triangle, each number is the sum of its upper left and upper right numbers.

Example:

Input: 5
 Output:
[
     [1],
    [1,1],
   [1,2,1],
  [1,3,3,1],
 [1,4,6,4,1]
]

Assumptions for Topic Analysis

The most clumsy solution to this question is a double loop, starting and ending at 1, with S(l)[n] = S(l-1)[n-1] + S(l-1)[n].Of course, it is obvious that this can also be answered as a dynamic planning question.

There's a hole here, it's an index, not line n

Write code validation

I. Dynamic Planning

Code:

/**
 * @param {number} numRows
 * @return {number[][]}
 */
var generate = function(numRows) {
    let res = []
    for(let i = 0; i < numRows; i++) {
        // All defaults are filled in with 1, which saves a lot of computation
        res.push(new Array(i+1).fill(1))
        // The third line needs to be modified before it starts
        for(j = 1; j < i; j++) {
            res[i][j] = res[i-1][j] + res[i-1][j-1]
        }
    }
    return res
};

Result:

  • 15/15 cases passed (60 ms)
  • Your runtime beats 85.2 % of javascript submissions
  • Your memory usage beats 55.52 % of javascript submissions (33.6 MB)
  • Time Complexity O(n^2)

Look up other people's solutions

Here you can see two different directions, one is recursion, because on a recursive card, the other is the binomial theorem.

I. Recursion

Code:

/**
 * @param {number} numRows
 * @return {number[][]}
 */
var generate = function (numRows) {
    let res = []

    function sub(row, numRows, arr) {
        let temp = []
        if (row < numRows) {
            for (let i = 0; i <= row; i++) {
                if (row === 0) {
                    temp.push(1)
                } else {
                    let left = i - 1 >= 0 ? arr[row - 1][i - 1] : 0
                    let right = i < arr[row - 1].length ? arr[row - 1][i] : 0
                    temp.push(left + right)
                }
            }
            arr.push(temp)
            sub(++row, numRows, arr)
            return arr
        } else {
            return arr
        }
    }
    return sub(0, numRows, res)
};

Result:

  • 15/15 cases passed (64 ms)
  • Your runtime beats 68.27 % of javascript submissions
  • Your memory usage beats 56.86 % of javascript submissions (33.6 MB)
  • Time Complexity O(n^2)

II. Binomial Theorem

The advantage is that line n can be calculated directly, using the binomial theorem formula.(a+b)^n has a total of n+1 items, and the coefficients of each item correspond to the nth row of Yang Hui triangle.The coefficient of term r equals the number of combinations C(n,r).

Code:

/**
 * @param {number} numRows
 * @return {number[][]}
 */
var generate = function(numRows) {
    var res = [];

    /**
     * Number of combinations
     * @param n
     * @param r
     * @returns {number}
     * @constructor
     */
    function C(n, r) {
        if(n == 0) return 1;
        return F(n) / F(r) / F(n - r);
    }
    /**
     * Factorial
     * @param n
     * @returns {number}
     * @constructor
     */
    function F(n) {
        var s = 1;
        for(var i = 1;i <= n;i++) {
            s *= i;
        }
        return s;
    }

    for (var i = 0;i < numRows;i++){
        res[i] = [];
        for (var j = 0;j < i + 1;j++){
            res[i].push(C(i, j));
        }
    }
    return res;
};

Result:

  • 15/15 cases passed (64 ms)
  • Your runtime beats 68.27 % of javascript submissions
  • Your memory usage beats 5.02 % of javascript submissions (34.3 MB)
  • Time Complexity O(n^2)

Thinking Summary

For math-sensitive developers, it is easy to think of using binomial theorems.But in my view, when I find a calculation rule, it is easy to think of using dynamic planning to solve the problem. I also recommend using dynamic planning to generate Yang Hui triangle.

119. Yang Hui Triangle II

Title Address

Title Description

Given a non-negative index k, where k is less than or equal to 33, returns the k-th row of Yang Hui triangle.

In Yang Hui triangle, each number is the sum of its upper left and upper right numbers.

Example:

Input: 3
 Output: [1,3,3,1]

Advanced:

Can you optimize your algorithm to O(k) spatial complexity?

Assumptions for Topic Analysis

The binomial theorem can be found directly in line n from other people's solutions above.In addition, we can also find the rule that there are actually several numbers in the first row, with the start and end being 1.You can also use dynamic planning to answer this question.

Write code validation

I. Dynamic Planning

Code:

/**
 * @param {number} rowIndex
 * @return {number[]}
 */
var getRow = function(rowIndex) {
    // rowIndex is an index, 0 equals the first row
    if (rowIndex === 0) return [1]
    let res = []
    for(let i = 0; i < rowIndex + 1; i++) {
        let temp = new Array(i+1).fill(1)
        // The third line needs to be modified before it starts
        for(let j = 1; j < i; j++) {
            temp[j] = res[j - 1] + res[j]
        }
        res = temp
    }
    return res
};

Result:

  • 34/34 cases passed (64 ms)
  • Your runtime beats 75.77 % of javascript submissions
  • Your memory usage beats 54.9 % of javascript submissions (33.8 MB)
  • Time Complexity O(n^2)

II. Binomial Theorem

Code:

/**
 * @param {number} rowIndex
 * @return {number[]}
 */
var getRow = function(rowIndex) {
    /**
     * Number of combinations
     * @param n
     * @param r
     * @returns {number}
     * @constructor
     */
    function C(n, r) {
        if(n == 0) return 1;
        return F(n) / F(r) / F(n - r);
    }
    /**
     * Factorial
     * @param n
     * @returns {number}
     * @constructor
     */
    function F(n) {
        var s = 1;
        for(var i = 1;i <= n;i++) {
            s *= i;
        }
        return s;
    }
    let res = []
    // Because it was calculated from the previous term, n in Item 1 is zero
    for (var i = 0;i < rowIndex + 1;i++){
        res.push(C(rowIndex, i));
    }
    return res;
};

Result:

  • 34/34 cases passed (52 ms)
  • Your runtime beats 99.12 % of javascript submissions
  • Your memory usage beats 41.18 % of javascript submissions (34.5 MB)
  • Time Complexity O(n)

Look up other people's solutions

Since symmetry is found for each row, you can also reverse the copy after half.

I. Reverse Replication

Code:

/**
 * @param {number} rowIndex
 * @return {number[]}
 */
var getRow = function(rowIndex) {
    // rowIndex is an index, 0 equals the first row
    if (rowIndex === 0) return [1]
    let res = []
    for(let i = 0; i < rowIndex + 1; i++) {
        let temp = new Array(i+1).fill(1)
        // The third line needs to be modified before it starts
        const mid = i >>> 1
        for(let j = 1; j < i; j++) {
            if (j > mid) {
                temp[j] = temp[i - j]
            } else {
                temp[j] = res[j - 1] + res[j]
            }
        }
        res = temp
    }
    return res
};

Result:

  • 34/34 cases passed (60 ms)
  • Your runtime beats 88.47 % of javascript submissions
  • Your memory usage beats 60.78 % of javascript submissions (33.7 MB)
  • Time Complexity O(n^2)

Thinking Summary

In fact, it is more like a mathematical problem, constantly finding rules to save operations, really "learn mathematics, physics and chemistry, walk around the world without fear".

(finished)

This is an original article, which may update your knowledge points and correct errors, so please keep the original source for easy tracing, avoid misleading old wrong knowledge, and have a better reading experience
If you can get some help, you are welcome to star t or fork
(For reprinting, please indicate the source: https://chenjiahao.xyz)

Topics: Javascript less