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
- 110. Balanced Binary Tree
- 111. Minimum depth of a binary tree
- 112. Sum of paths
- 118. Yang Hui Triangle
- 119. Yang Hui Triangle II
Easy
110. Balanced Binary Tree
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 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 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 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 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)