Making the warehouse
Online reading address

Here’s what you need to know before you practice

Applicable people

If you’re preparing for an interview and you don’t know where to start with a lot of complicated data structures and algorithms;

If you have learned some basic knowledge of data structures or algorithms before, but do not understand clearly, let alone complete the design of algorithms, want to consolidate this knowledge;

If you have a short period of work experience, but always spend all day in wheels, tuning API, want to take a look at the underlying source code is often due to insufficient programming internal forces and give up;

If you have heard of LeetCode this website, want to brush through to the end, towards the peak of the algorithm, but because of the vast amount of questions and lack of systematic training feel powerless, three days of fishing and two days of exposure to the net, and then feel anxious, even give up……

If any of the above is true for you, you’ve come to the right place. As a well-known front-end blogger in the circle, I would like to share my process of systematic combing and practice with my influence, hoping to help more people who meet with similar difficulties like me, so that you will have less unnecessary trouble.

The language used throughout is JavaScript, so the title says front-end XXX, but in fact, as you know, the data structure and algorithm are mainly a test of one’s thinking, as for the language, it does not use any of the advanced syntax features of JS, as long as some programming experience, can understand the code is completely no problem. You can rest assured of that.

Algorithm doesn’t work, right?

Before I start this exercise, LET me clarify my point so that you don’t get any more confused about data structures and algorithms or this series.

I’m sure there are some of you who are preparing for an interview, so you’ve heard about the interview building rocket, the job turning screw, and a lot of people have used this phrase to criticize the algorithmic interview of some of the biggest Internet companies today, so there is no use learning algorithms except for the interview.

I am not totally against this sentence, because now with the development of the ecological technology, you walk in the forefront in the field of Daniel have to rebuilt the wheels, general business problems directly take somebody else’s plan to use is ok, I also have seen in a word, just beginning to feel bullshit, think later feel so a truth:

Any technology that needs to cross a certain IQ threshold to master will not easily become popular.

In other words: technology becomes easier, more accessible, and more popular.

This is also true of current technology frameworks: simple enough to work, simple enough that you don’t need to know the complex details underneath.

So what’s your value as an intelligent and talented programmer?

In my opinion, the value depends on the problem you can solve. If you draw a simple Button according to the design draft, you can complete it, other front ends can also complete it, and even students at the back and back ends can almost complete the effect, then what about personal value? It just makes no difference what most people can easily do in a replaceable position.

But now if the face is a complex engineering problem, need you to develop a scaffolding tools, auxiliary business transformation framework source code to improve the extensibility of the project, or face severe performance problems immediately to analyze the reason, then gives the thinking of solving and balance in different elements, these are not an amateur player can do in a short time, This is where your value comes in.

Going back to the algorithm itself, it represents part of your ability to solve more complex problems.

It may not be easy to understand. Let’s take Vue as an example. If you have never been exposed to the concepts of depth-first traversal and recursion, and have not seen the corresponding code, you can hardly understand the source code of the entire patch of the virtual DOM. If you don’t have a systematic grasp of this feature, it’s hard to understand why you need to use the stack to check if the tag is closed properly during Vue template compilation. Similarly, if you don’t have code experience with backtracking, it is difficult to understand how the optimization phase of Vue template compilation checks for non-static child nodes and marks the parent nodes during depth-first traversal from parent to child. Also, if you didn’t know what the LRU cache elimination algorithm was, you might look at the keep-Alive implementation and wonder:

if (cache[key]) {
  vnode.componentInstance = cache[key].componentInstance
  // make current key freshest
  remove(keys, key)
  keys.push(key)
}
Copy the code

If the cache hit, why maintain an array of keys, and delete the key from the array, and put it at the end?

If you’ve had the basis of the algorithm before, you might think this is a very natural thing to do.

See? Do you think you can write great “star” projects without a solid foundation in data structures and algorithms?

Of course, it is not only the front end, I think the server side is also the same situation, here is not too many examples.

So folks, I think basic algorithmic ability is not a plus for an engineer who wants to solve complex problems, it’s a must. Algorithms are useful.

How to practice systematically?

I’m going to share some tips, or methods, for practicing data structures and algorithms, in two key words: systems and practices.

Special breakthrough

How to achieve systematic training?

I want to order according to the LeetCode one problem to brush is definitely not a problem system, mostly is the adjacent a few topic has no connection, this topic makes a list of related, the next question is a hash table, and then the next question and a binary search tree, thinking jump, on the one hand, may you very weak, the base of various data structures and algorithms to understand is not deep, Repeatedly jump to the place you are not familiar with, will frustrate your confidence, increase anxiety, and even direct to discourage, on the other hand, may meet a little more difficult list questions, you will not, you may wonder: is not just written out a list of questions? Why not now? It’s prone to self-doubt, which can also affect your drive to train.

Therefore, I think it is a relatively reasonable strategy to break through the training by topic. At the same time, it will make you master faster, so that it is easier to solve similar problems, enhance self-confidence.

Another thing to introduce is the thematic system of this series, which is divided into these modules:

This share is the linked list, stack and queue and binary tree part.

Deliberate practice

The book “Outliers: A Different Kind of Success Revelation” talks about the theory of 10,000 hours of deliberate practice to become a master, and a lot of people have talked about this theory since. I heard about it a few years ago, but it was in the process of training the algorithm that I really realized its practical significance, and it was after a lot of detours. You can see how hard it is to get from theory to practice.

My understanding of deliberate practice, when applied to the training of algorithms, is two points:

  1. Often do problems you can’t do

  2. A variety of solutions in turn to maximize the value of a problem

Deliberate practice An important point is to get out of your comfort zone. For practicing algorithm is the same, why a lot of people think brush algorithm is useless, it is because they are always doing their familiar topic, in their familiar way, squatting in their comfort zone, to do so is more significance is not big, but if you are always doing their unskilled topic, using different methods, is quite helpful for the growth of their own thinking, So I think to grasp the regular to do not do the problem, the more the better, of course, time is limited, we need to choose according to their own needs.

The reason I call this series exercises rather than brushes is because the essence of practice is practice, not just AC. Like this one:

And for those of you who have experience, it’s very easy to solve it recursively. But, have you thought about how to do it in a non-recursive way? If you can use non-recursion is to achieve once, I believe that the help and improvement will be much greater than the recursive solution.

By the way, all of the code in this series is original and not only presents recursive code, but also non-recursive solutions in the vast majority of cases, to get the most out of a problem and practice it rather than brushing it.

Is the last of the last, I want to emphasize: for the science of uniting the practice of internal work, no video or column is only auxiliary function, is the most important thing is their persistence and independent thinking, if you pass this series I can let oneself ability of algorithm to the next level, or something, you should thank you. If you think this series is good, I hope you can enter the GitHub address and give this project a star. Thank you very much!

List article

Reverse a linked list

Reverse linked list there are three questions for you to train on. Are the reverse of the single linked list in situ, two a group of reverse linked list and K a group of reverse linked list, the difficulty from the step up.

And in the interview whenever encountered a list, the frequency of the reverse class of questions is also one of the first or second, so it as a list of the opening training type, I hope we can cause enough attention 💪.

No.1 simple reverse linked list

Inverts a single linked list.

Example:

Input: 1->2->3->4->5->NULL Output: 5->4->3->2->1->NULLCopy the code

Source: LeetCode problem 206

Cyclic solution

This question is a classic topic in the list, fully reflect the list of data structure operation idea is simple, but the implementation is not so simple characteristics.

What should we pay attention to in the implementation?

Save subsequent nodes. As a beginner, it’s easy to point the next pointer of the current node directly to the previous node, but the pointer to the next node of the current node is lost. Therefore, you need to save the next node during the traversal, and then operate on the next point.

The linked list structure sound is defined as follows:

function ListNode(val) {
  this.val = val;
  this.next = null;
}
Copy the code

The implementation is as follows:

/** * @param {ListNode} head * @return {ListNode} */
let reverseList =  (head) = > {
    if(! head)return null;
    let pre = null, cur = head;
    while (cur) {
        // Key: save the value of the next node
        let next = cur.next;
        cur.next = pre;
        pre = cur;
        cur = next;
    }
    return pre;
};
Copy the code

Because the logic is simple, the code is straightforward. But it’s not enough to just write it. For linked list problems, the custom of boundary checking can help us further ensure the quality of the code. To be specific:

  • When the head node is empty, we have processed it, via ✅
  • When the list contains onlyA nodeAt this point we want to return this node directly, in the case of the above code, after entering the looppreTo be an assignmentcur, that is,head, through ✅

Running in LeetCode, successful AC ✌

However, as a systematic training, it is too hasty to just let the program pass. We will try to solve the same problem in different ways in the future, so as to achieve a comprehensive effect. It is also a development of our own thinking, and sometimes we may reach a better solution.

Recursive solution

Since the previous idea has been very clear, so here we paste the code, you have a good experience:

let reverseList = (head) = >{
  let reverse = (pre, cur) = > {
    if(! cur)return pre;
    // Save the next node
    let next = cur.next;
    cur.next = pre;
    return reverse(cur, next);
  }
  return reverse(null, head);
}
Copy the code

No.2 interval reversal

Inverts the list from position m to n. Use one scan to complete the reversal.

Note: 1 ≤ M ≤ N ≤ Length of the list.

Example:

Input: 1 - > 2 - > 3 - > 4 - > 5 - > NULL, m = 2, n = 4 output: 1 - > > 4-3 - > 2 - > 5 - > NULLCopy the code

Problem 92

Train of thought

This problem compared to a whole list of reverse problems, in fact, the soup is not changed. We still have two types of solutions: cyclic solutions and recursive solutions.

The important thing to note is the preservation (or recording) of the front and back nodes. What does that mean? Look at this picture and you’ll see.

The definition of the front node and the back node, you should be able to see it more clearly on the graph, and we will use it a lot later.

The reverse operation has been dismantled in the last problem, so I won’t repeat it here. It should be noted that the work after the reversal is actually a grafting process for the whole interval after the reversal. First, the next of the former node points to the end of the interval, and then the next of the beginning of the interval points to the later node. Therefore, there are four nodes that need to be paid attention to in this problem: the front node, the back node, the interval starting point and the interval ending point. Now let’s get down to the actual coding.

Circulating solution

/** * @param {ListNode} head * @param {number} m * @param {number} n * @return {ListNode} */
var reverseBetween = function(head, m, n) {
    let count = n - m;
    let p = dummyHead = new ListNode();
    let pre, cur, start, tail;
    p.next = head;
    for(let i = 0; i < m - 1; i ++) {
        p = p.next;
    }
    // Save the previous node
    front = p;
    // Save the first interval node at the same time
    pre = tail = p.next;
    cur = pre.next;
    // Interval reversal
    for(let i = 0; i < count; i++) {
        let next = cur.next;
        cur.next = pre;
        pre = cur;
        cur = next;
    }
    // Next points to the end of the interval
    front.next = pre;
    // next of the first node of the interval points to the second node (cur after loop is the first node after the interval, i.e.
    tail.next = cur;
    return dummyHead.next;
};
Copy the code

The recursive method

The only difference with the recursive solution is that the interval is handled by a recursive program, and you can also review the implementation of recursive inversion.

var reverseBetween = function(head, m, n) {
  // Reverse the function recursively
  let reverse = (pre, cur) = > {
    if(! cur)return pre;
    // Save the next node
    let next = cur.next;
    cur.next = pre;
    return reverse(cur, next);
  }
  let p = dummyHead = new ListNode();
  dummyHead.next = head;
  let start, end; // The first and last nodes of the interval
  let front, tail; // Front node and back node
  for(let i = 0; i < m - 1; i++) {
    p = p.next;
  }
  front = p; // Save the previous node
  start = front.next;
  for(let i = m - 1; i < n; i++) {
    p = p.next;
  }
  end = p;
  tail = end.next; // Save the node
  end.next = null;
  // The front node points to the interval head, and the interval head points to the back node
  front.next = reverse(null, start);
  start.next = tail;
  return dummyHead.next;
}
Copy the code

No.3 Two sets of reverse lists

Given a linked list, swap the adjacent nodes in pairs and return the swapped list.

You can’t just change the values inside a node, but actually switch nodes.

Problem 24

Example:

Given 1->2->3->4, you should return 2->1->4->3.Copy the code

Train of thought

As shown in the figure, we first set up a virtual head node (dummyHead) to assist our analysis.

First, put p at dummyHead and record the nodes of p.ext and p.ext. Next, i.e. Node1 and node2.

Next = node2.next, effect:

Next = node1

Finally, dummyHead.next = node2, and the flip is complete. Meanwhile, the p pointer points to node1, and the effect is as follows:

If p.ext or p.ext. Next is null, no new set of nodes can be found, the loop ends.

Cycle to solve

The idea is clear, in fact, the implementation is relatively easy, the code is as follows:

var swapPairs = function(head) {
    if(head == null || head.next == null) 
        return head;
    let dummyHead = p = new ListNode();
    let node1, node2;
    dummyHead.next = head;
    while((node1 = p.next) && (node2 = p.next.next)) {
        node1.next = node2.next;
        node2.next = node1;
        p.next = node2;
        p = node1;
    }
    return dummyHead.next;
};
Copy the code

recursively

var swapPairs = function(head) {
    if(head == null || head.next == null)
        return head;
    let node1 = head, node2 = head.next;
    node1.next = swapPairs(node2.next);
    node2.next = node1;
    return node2;
};
Copy the code

Does the code feel very concise after using recursion? 😃 😃 😃

I hope you can have a good experience of the recursive invocation process, I believe that understanding will be a great improvement for yourself.

No.4 A group of K reverse linked list

I give you a list, and I flip it every k nodes, and I ask you to return the list.

K is a positive integer whose value is less than or equal to the length of the list.

If the total number of nodes is not an integer multiple of K, leave the last remaining nodes in their original order.

Example:

Given this list: 1 - > 2 - > 3 - > when k = 2, 4 - > 5, should return: 4 - > 2 - > 1 - > 3 - > 5 when k = 3, should return: 3 - > 2 - > 1 - > 4 - > 5Copy the code

Description:

  • Your algorithm can only use constant extra space.
  • You can’t just change the values inside a node, but actually switch nodes.

Problem 25

Train of thought

The idea is similar to No.3 in two a group of rollovers. The only difference is that in the case of two groups, each group only needs to reverse two nodes, while in the case of K groups, the corresponding operation is to reverse the list of K elements.

The recursive method

I think the recursive method is easier to understand, so let’s paste the recursive method code first.

The following code comments refer to the concepts’ first node ‘, ‘tail node’ and so on for the list before inversion.

/** * @param {ListNode} head * @param {number} k * @return {ListNode} */
var reverseKGroup = function(head, k) {
    let pre = null, cur = head;
    let p = head;
    // The following loop checks whether the following elements can form a group
    for(let i = 0; i < k; i++) {
        if(p == null) return head;
        p = p.next;
    }
    for(let i = 0; i < k; i++){
        let next = cur.next;
        cur.next = pre;
        pre = cur;
        cur = next;
    }
    // pre is the last node in the group and cur is the starting point for the next group
    head.next = reverseKGroup(cur, k);
    return pre;
};
Copy the code

Circulating solution

It’s all in the comments.

var reverseKGroup = function(head, k) {
    let count = 0;
    // See if you can form a group and count the number of elements
    for(letp = head; p ! =null; p = p.next) {
        if(p == null && i < k) return head;
        count++;
    }
    let loopCount = Math.floor(count / k);
    let p = dummyHead = new ListNode();
    dummyHead.next = head;
    // Divide into loopCount groups and reverse each group
    for(let i = 0; i < loopCount; i++) {
        let pre = null, cur = p.next;
        for(let j = 0; j < k; j++) {
            let next = cur.next;
            cur.next = pre;
            pre = cur;
            cur = next;
        }
        // Currently pre is the end node of the group, and cur is the first node of the next group
        let start = p.next;// Start is the first node in the group
        // Start threading! The idea is exactly the same as in the two cases
        p.next = pre;
        start.next = cur;
        p = start;
    }
    return dummyHead.next;
};
Copy the code

Circular linked list

No.1 How to detect the link list forming a ring?

Given a linked list, determine whether a ring is formed in the list.

Train of thought

Idea 1: Loop once, use the Set data structure to save the node, use the memory address of the node to judge the weight, if the same node passed twice, it has formed a ring.

Train of thought 2: use the fast and slow pointer, the fast pointer takes two steps at a time, and the slow pointer takes one step at a time. If the two meet, it indicates that a loop has been formed.

Now, you might be wondering, why is idea two always meeting in a ring?

In fact, if there is a loop, both of them must go into the loop at the same time, so in the loop, take the slow pointer as the reference frame, and every time the fast pointer moves forward, it will eventually go back to the origin, which is the position of the slow pointer, so that the two meet. If there is no ring, then the two are further and further apart and never meet.

Now let’s do it programmatically.

Method 1: Set the severity

/** * @param {ListNode} head * @return {boolean} */
var hasCycle = (head) = > {
    let set = new Set(a);let p = head;
    while(p) {
        // If the same node is encountered again, there is a ring
        if(set.has(p)) return true;
        set.add(p);
        p = p.next;
    }
    return false;
}
Copy the code

Method two: fast pointer

var hasCycle = function(head) {
    let dummyHead = new ListNode();
    dummyHead.next = head;
    let fast = slow = dummyHead;
    // Zero nodes or one node, there must be no loop
    if(fast.next == null || fast.next.next == null) 
        return false;
    while(fast && fast.next) {
        fast = fast.next.next;
        slow = slow.next;
        // The two met
        if(fast == slow) {
            return true; }}return false;
};
Copy the code

No.2 How to find the starting point of the loop

Given a linked list, return the first node where the list started to enter the loop. Null is returned if the list is loopless.

** Description: ** is not allowed to modify the given list.

Thought analysis

Now that we know how to tell if a ring is present, how do we find the nodes of the ring? Let’s analyze a wave.

This may seem trivial, but let’s abstract it further:

Let the fast and slow pointer go x seconds, and the slow pointer go once a second.

For the fast pointer, there is 2x -l = m * S + Y —–①

For the slow pointer, there is: x-l = n * S + Y —–②

Where, m and n are natural numbers.

① – ② * 2

L = (m-n) * s-y —–③

Ok, so this is a very important equation. We now assume that we have a new pointer at the leftmost end of segment L, and the slow Pointers are still at the meeting point.

Let the new pointer and the slow pointer take one step at a time, so when the new pointer takes L steps and reaches the starting point of the loop, meanwhile, let’s see what happens to the slow pointer.

According to formula ③, the slow pointer travels by (m-n) * s-y units, and the position when it meets is Y, taking the starting point of the ring as a reference. Now, from Y + (m-n) * s-y, that is, (m-n) * S, we know that the slow pointer actually travels a full (m-n) circle with reference to the starting point of the ring. In other words, the slow pointer now reaches the starting point of the loop. When the fast and slow Pointers meet, let the new pointer start from the beginning, and the slow pointer advance at the same time, and each step forward, where the two meet, is the starting point of the loop. : : :

Programming to realize

Once you understand the principle, it’s a lot easier to implement.

/** * @param {ListNode} head * @return {ListNode} */
var detectCycle = function(head) {
    let dummyHead = new ListNode();
    dummyHead.next = head;
    let fast = slow = dummyHead;
    // Zero nodes or one node, there must be no loop
    if(fast.next == null || fast.next.next == null) 
        return null;
    while(fast && fast.next) {
        fast = fast.next.next;
        slow = slow.next;
        // The two met
        if(fast == slow) {
           let p = dummyHead;
           while(p ! = slow) { p = p.next; slow = slow.next; }returnp; }}return null;
};
Copy the code

List to merge

No.1 combines two ordered linked lists

Merge two ordered lists into a new ordered list and return. The new list is formed by concatenating all the nodes of the given two lists.

Example:

Input: 1->2->4; Output: 1->1->2->3->4->4Copy the code

Question 21

The recursive method

The recursive solution is easier to understand, so let’s do it recursively:

/** * @param {ListNode} l1 * @param {ListNode} l2 * @return {ListNode} */
var mergeTwoLists = function(l1, l2) {
    const merge = (l1, l2) = > {
        if(l1 == null) return l2;
        if(l2 == null) return l1;
        if(l1.val > l2.val) {
            l2.next = merge(l1, l2.next);
            return l2;
        }else {
            l1.next = merge(l1.next, l2);
            returnl1; }}return merge(l1, l2);
};
Copy the code

Circulating solution

var mergeTwoLists = function(l1, l2) {
    if(l1 == null) return l2;
    if(l2 == null) return l1;
    let p = dummyHead = new ListNode();
    let p1 = l1, p2 = l2;
    while(p1 && p2) {
        if(p1.val > p2.val) {
            p.next = p2;
            p = p.next;
            p2 = p2.next;
        }else{ p.next = p1; p = p.next; p1 = p1.next; }}// Be sure to check the rest of the loop once it is complete
    if(p1) p.next = p1;
    else p.next = p2;
    return dummyHead.next;
};
Copy the code

Merge K ordered linked lists

Merge k sorted lists and return the merged sorted list. Analyze and describe the complexity of the algorithm.

Example:

Input: [1 - > 4 - > 5, 1 - > 3 - > 4, 2 - > 6] output: 1 - > 1 - > 2 - > 3 - > 4 - > 4 - > 5 - > 6Copy the code

Question 23

Top-down (recursive) implementation

/** * @param {ListNode[]} lists * @return {ListNode} */
var mergeKLists = function(lists) {
    // The above is already implemented
    var mergeTwoLists = function(l1, l2) {/* * * has been implemented above};
    const _mergeLists = (lists, start, end) = > {
        if(end - start < 0) return null;
        if(end - start == 0)return lists[end];
        let mid = Math.floor(start + (end - start) / 2);
        return mergeTwoList(_mergeLists(lists, start, mid), _mergeLists(lists, mid + 1, end));
    }
    return _mergeLists(lists, 0, lists.length - 1);
};
Copy the code

Bottom-up implementation

I want to remind you that in the bottom-up implementation, I bound a dummyHead pointer (dummyHead) to each linked list. Why did I do that?

For example, after l1 and L2 are merged, the head pointer of the merged list is directly l1 dummyhead. next, which means that both lists are merged into L1, which facilitates the subsequent merge operation.

var mergeKLists = function(lists) {
    var mergeTwoLists = function(l1, l2) {/* * * has been implemented above};
    // Boundary case
    if(! lists || ! lists.length)return null;
    // Set of virtual header Pointers
    let dummyHeads = [];
    // Initialize the virtual header pointer
    for(let i = 0; i < lists.length; i++) {
        let node = new ListNode();
        node.next = lists[i];
        dummyHeads[i] = node;
    }
    // Merge from the bottom up
    for(let size = 1; size < lists.length; size += size){
        for(let i = 0; i + size < lists.length; i +=2* size) { dummyHeads[i].next = mergeTwoLists(dummyHeads[i].next, dummyHeads[i + size].next); }}return dummyHeads[0].next;
};
Copy the code

Multiple list merge here to complete the implementation, here by the way to tell you the way this merge is also the core code for the list merge sort. I hope you can have a good experience of top-down and bottom-up two different implementation details, I believe to your programming skills is a good improvement.

Find the middle node of the list

Determine palindrome lists

Please determine whether a single linked list is a palindrome list.

Example 1:

Input: 1->2 Output:false
Copy the code

Example 2:

Input: 1->2->2->1true
Copy the code

Can you solve this problem with O(n) time and O(1) space?

Question 234

Thought analysis

This is a fairly simple problem, if you ignore the performance constraints. But given the complexity of O(n) time and O(1) space, it’s worth stopping and thinking.

There is no way to access the previous nodes, so we have to do something else:

Find the midpoint of the list, and then reverse the latter half, you can compare and draw a conclusion. So let’s do a wave.

Code implementation

In fact, the key part of the code is to find the midpoint. Strong-arm reaction first:

  let dummyHead = slow = fast = new ListNode();
  dummyHead.next = head;
  // Look, it's time to find the middle point
  while(fast && fast.next) {
      slow = slow.next;
      fast = fast.next.next;
  }
Copy the code

And you might be wondering, why is the boundary set like this?

Let’s think about it for an odd number of nodes and an even number of nodes.

  • When the number of linked list nodes is odd

Try to simulate, when FAST is empty, stop the loop, the state is as follows:

  • When the number of linked list nodes is even

For the two conditions, in the case of odd numbers, fast is always empty first, and in the case of even numbers, fast. Next always appears first.

That is to say: once fast is empty, the number of linked list nodes must be odd, otherwise it is even. Therefore, the two cases can be combined to discuss, when fast is empty or fast. Next is empty, the loop can be terminated.

The full implementation is as follows:

/** * @param {ListNode} head * @return {boolean} */
var isPalindrome = function(head) {
    let reverse = (pre, cur) = > {
        if(! cur)return pre;
        let next = cur.next;
        cur.next = pre;
        return reverse(cur, next);
    }
    let dummyHead = slow = fast = new ListNode();
    dummyHead.next = head;
    // Look for the middle point, gold template
    while(fast && fast.next) {
        slow = slow.next;
        fast = fast.next.next;
    }
    let next = slow.next;
    slow.next = null;
    let newStart = reverse(null, next);
    for(letp = head, newP = newStart; newP ! =null; p = p.next, newP = newP.next) {
        if(p.val ! = newP.val)return false;
    }
    return true;
};
Copy the code

Stack and queue pieces

Stack & recursion

Effective brackets

Given a string containing only ‘(‘, ‘)’, ‘{‘, ‘}’, ‘[‘, ‘]’, determine whether the string is valid.

A valid string must meet the following requirements:

The opening parenthesis must be closed with a closing parenthesis of the same type. The opening brackets must be closed in the correct order. Note that an empty string can be considered a valid string.

Example:

Input:"()"Output:true
Copy the code

Question 20

Code implementation

/** * @param {string} s * @return {boolean} */
var isValid = function(s) {
    let stack = [];
    for(let i = 0; i < s.length; i++) {
        let ch = s.charAt(i);
        if(ch == '(' || ch == '[' || ch == '{') 
            stack.push(ch);
        if(! stack.length)return false;
        if(ch == ') '&& stack.pop() ! = ='(') return false;
        if(ch == '] '&& stack.pop() ! = ='[' ) return false;
        if(ch == '} '&& stack.pop() ! = ='{') return false;
    }
    return stack.length === 0;
};
Copy the code

Multidimensional array flatten

Convert a multidimensional array to a one-dimensional array.

Example:

[1, [2, [3, [4, 5]], 6] -> [1, 2, 3, 4, 5, 6]Copy the code

Code implementation

/** * @constructor * @param {NestedInteger[]} nestedList * @return {Integer[]} */
let flatten = (nestedList) = > {
    let result = [];
    let fn = function (target, ary) {
        for (let i = 0; i < ary.length; i++) {
            let item = ary[i];
            if (Array.isArray(ary[i])) {
                fn(target, item);
            } else {
                target.push(item);
            }
        }
    }
    fn(result, nestedList)
    return result;

Copy the code

At the same time, the reduce method can be used to solve the problem in one row, which is very concise.

let flatten = (nestedList) = >  nestedList.reduce((pre, cur) = > pre.concat(Array.isArray(cur) ? flatten(cur): cur), [])
Copy the code

Binary tree sequence traversal

The sequence traversal of binary trees is for the next chapter, but the nature of queues is too obvious, so this kind of problem is used to practice the operation of queues. This will not only involve ordinary sequence traversal, but also deformation, so that you can understand it thoroughly.

Normal level traversal

Given a binary tree, return the values of the nodes traversed hierarchically. (that is, access all nodes layer by layer, from left to right).

Example:

  3
  / \
9  20
  /  \
  15   7
Copy the code

The result should be output:

[[3], [9,20], [15,7]Copy the code

Source: LeetCode question 102

implementation

/** * @param {TreeNode} root * @return {number[][]} */
var levelOrder = function(root) {
    if(! root)return [];
    let queue = [];
    let res = [];
    let level = 0;
    queue.push(root);
    let temp;
    while(queue.length) {
        res.push([]);
        let size = queue.length;
        // Note: size -- is a very important technique in hierarchy traversal
        while(size --) {
            / / out of the team
            let front = queue.shift();
            res[level].push(front.val);
            / / team
            if(front.left) queue.push(front.left);
            if(front.right) queue.push(front.right);
        }        
        level++;
    }
    return res;
};
Copy the code

Zigzag hierarchical traversal of binary trees

Given a binary tree, return a zigzag hierarchical traversal of its node values. (that is, first from left to right, then from right to left to the next layer, and so on, alternating between layers).

Such as:

Given a binary tree [3,9,20,null,null,15,7], the output should look like this:

    3
   / \
  9  20
    /  \
   15   7
Copy the code

Return zigzag hierarchy traversal as follows:

[[3],
  [20.9],
  [15.7]]Copy the code

Question 103

Train of thought

This is a slightly different idea, but if you get the idea of hierarchical traversal, it’s pretty easy.

Code implementation

var zigzagLevelOrder = function(root) { if(! root) return []; let queue = []; let res = []; let level = 0; queue.push(root); let temp; while(queue.length) { res.push([]); let size = queue.length; While (size --) {let front = queue.shift(); res[level].push(front.val); if(front.left) queue.push(front.left); if(front.right) queue.push(front.right); } if(level % 2) res[level].reverse(); level++; } return res; };Copy the code

Right view of the binary tree

Given a binary tree, imagine yourself standing on the right side of it and return the values of the nodes visible from the right, in order from top to bottom.

Example:

Input: [5, 1, 2, 3, null, null, 4] output: [1, 3, 4] explanation: < 1-2 3 < / \ - \ \ 5 4 < -- -- -Copy the code

Source: LeetCode problem 199

Train of thought

Right view? If you think of it in terms of DFS, or depth-first search, it’s incredibly painful. Yeah, that’s how I got ripped off 🙂

But if you use the breadth-first search idea, that is, in order to traverse the way, it is easy to solve this problem.

Code implementation

/** * @param {TreeNode} root * @return {number[]} */
var rightSideView = function(root) {
    if(! root)return [];
    let queue = [];
    let res = [];
    queue.push(root);
    while(queue.length) {
        res.push(queue[0].val);
        let size = queue.length;
        while(size --) {
            // A loop of size is a loop of one level, where only the rightmost node is taken
            let front = queue.shift();
            if(front.right) queue.push(front.right);
            if(front.left) queue.push(front.left); }}return res;
};
Copy the code

Diagram BFS traversal is not authorized

Perfect square

Given a positive integer n, find several perfect squares (e.g., 1, 4, 9, 16…). So that their sum is equal to n. You need to minimize the number of perfect squares that make up the sum.

Example:

Input: n = 12 Output: 3 Description: 12 = 4 + 4 + 4.Copy the code

Question 279

Train of thought

The easiest way to think about this is dynamic programming, and we’ll break it down later. In fact, using queues to model graphs can also be solved smoothly with breadth-first traversal.

You might be a little confused by this picture, but let me explain it a little bit.

In this graph of non-entitlements, each point points to the last node it might have passed through. For example, for 5, it could be the conversion of 4 plus 1 squared, or it could be the conversion of 1 plus 2 squared, so it’s related to both 1 and 2, and so on.

So what we’re going to do now is find the shortest number of lines to go from n to 0.

For example, when n = 8, we need to find its neighbor nodes 4 and 7. At this time, the number of steps to reach 4 and 7 is 1. Then, starting from 4 and 7, 4 finds neighbor nodes 3 and 0, and the number of steps to reach 3 and 0 is 2. Return the number of steps to 0 of 2.

Talk is cheap, show me your code. Let’s take a step by step approach to this search.

implementation

Let’s implement the first version of the code.

/** * @param {number} n * @return {number} */
var numSquares = function(n) {
    let queue = [];
    queue.push([n, 0]);
    while(queue.length) {
        let [num, step] = queue.shift();
        for(let i = 1; ; i ++) {
            let nextNum = num - i * i;
            if(nextNum < 0) break;
            // Return step + 1 for the last step
            if(nextNum == 0) return step + 1;
            queue.push([nextNum, step + 1]); }}// There is no need to return another value, because 1 is also a perfect square, and all numbers can be combined with 1
};
Copy the code

This solution is functionally fine, but it hides a huge performance problem that you can test with LeetCode, basically timeouts.

So why is this a problem?

It comes out in this line of code:

queue.push([nextNum, step + 1]);
Copy the code

Anything greater than 0, I’m going to put in the queue. 2-1 = 1, 5-4 = 1, 9-8 = 1…… It’s going to repeat a lot of 1’s, and so on, and so on, and so on, and so on.

This large number of repeated numbers not only consumes more loop times, but also puts more pressure on memory space.

Therefore, we need to mark the numbers that have been pushed into the queue to avoid repeated pushing. The improved code is as follows:

var numSquares = function(n) {
    let map = new Map(a);let queue = [];
    queue.push([n, 0]);
    map.set(n, true);
    while(queue.length) {
        let [num, step] = queue.shift();
        for(let i = 1; ; i++) {
            let nextNum = num - i * i;
            if(nextNum < 0) break;
            if(nextNum == 0) return step + 1;
            // nextNum has not been accessed
            if(! map.get(nextNum)){ queue.push([nextNum, step +1]);
                // The tag has already been accessed
                map.set(nextNum, true); }}}};Copy the code

randomly

Given two words (beginWord and endWord) and a dictionary, find the length of the shortest conversion sequence from beginWord to endWord. The conversion must follow the following rules:

  • Only one letter can be changed per conversion.
  • The intermediate word in the transformation must be a dictionary word.

Description:

  1. If no such sequence of transformations exists, return 0.
  2. All words have the same length.
  3. All words consist of lowercase letters only.
  4. There are no duplicate words in the dictionary.
  5. You can assume that beginWord and endWord are non-empty and that they are not the same.

Example:

Enter beginWord ="hit",
endWord = "cog",
wordList = ["hot"."dot"."dog"."lot"."log"."cog"[Output: 5 Explanation: a shortest transformation sequence is"hit" -> "hot" -> "dot" -> "dog" -> "cog"Returns its length 5.Copy the code

Problem 127

Train of thought

This is one of the more typical graphically modeled problems. If each word is a node, it is a neighboring node as long as it is different from the word by only one letter.

Here we can do the traversal by means of BFS. The implementation is as follows:

Code implementation

/** * @param {string} beginWord * @param {string} endWord * @param {string[]} wordList * @return {number} */
var ladderLength = function(beginWord, endWord, wordList) {
    // Whether two words are adjacent in the diagram
    const isSimilar = (a, b) = > {
        let diff = 0
        for(let i = 0; i < a.length; i++) {
            if(a.charAt(i) ! == b.charAt(i)) diff++;if(diff > 1) return false; 
        }
        return true;
    }
    let queue = [beginWord];
    let index = wordList.indexOf(beginWord);
    if(index ! = =- 1) wordList.splice(index, 1);
    let res = 2;
    while(queue.length) {
        let size = queue.length;
        while(size --) {
            let front = queue.shift();
            for(let i = 0; i < wordList.length; i++) {
                if(! isSimilar(front, wordList[i]))continue;
                / / found
                if(wordList[i] === endWord) {
                    return res;
                }
                else {
                    queue.push(wordList[i]);
                }
                WordList [I] = wordList[I] = wordList[I
                // This step is critical for performance optimization, otherwise 100% time out
                wordList.splice(i, 1); i --; }}Steps / / + 1
        res += 1;
    }
    return 0;
};
Copy the code

Implementing a priority queue

A priority queue is a special type of queue whose bottom layer uses the structure of the heap so that the first element of the queue is always the highest priority each time it is added or removed. It is up to us to decide what fields to prioritize and how to compare them.

To implement a priority queue, first implement a heap structure.

A note about the heap

You may not have seen a heap before, but it’s a very simple structure. It’s essentially a binary tree. But the binary tree is more special, in addition to use an array to store in turn each node (the node corresponding to the array subscript and sequence traversal sequence number), it need to make sure that any child more priority than a parent node, it is also its most critical nature, because to ensure the root element must be the highest priority.

Here’s an example:

Now the array of the heap is: [10, 7, 2, 5, 1]

This results in two very critical operations – siftUp and siftDown.

For the siftUp operation, if you have a normal heap, where any parent element has a higher priority than the child element, and you add another element to the end of the heap array, you might not fit the structure of the heap. So now I compare the newly added node to its parent, and if the parent has a lower priority than it, then the two are switched up and up to the root node, thus ensuring the correct structure of the heap. And that operation is called siftUp.

SiftDown is an operation in the opposite direction, from top to bottom, the same principle, also to ensure the correct structure of the heap.

Implement a maximum heap

The maximum heap, or the element at the top of the heap, is the element with the highest priority.

// Use the maximum heap example to implement a wave
/**
* @param {number[]} nums
* @param {number} k
* @return {number[]}
*/
class MaxHeap {
  constructor(arr = [], compare = null) {
    this.data = arr;
    this.size = arr.length;
    this.compare = compare;
  }
  getSize() {
    return this.size;
  }
  isEmpty() {
    return this.size === 0;
  }
  // Add the element
  add(value) {
    this.data.push(value);
    this.size++;
    // siftUp the added element
    this._siftUp(this.getSize() - 1);
  }
  // Find the highest priority element
  findMax() {
    if (this.getSize() === 0)
      return;
    return this.data[0];
  }
  // Let the element with the highest priority (that is, the first element) out of the queue
  extractMax() {
    // 1. Save the first element of the queue
    let ret = this.findMax();
    // 2. Switch the first and last elements
    this._swap(0.this.getSize() - 1);
    // 3. Kick out the end of the queue
    this.data.pop();
    this.size--;
    // 4. New team leader siftDown
    this._siftDown(0);
    return ret;
  }

  toString() {
    console.log(this.data);
  }
  _swap(i, j) {
    [this.data[i], this.data[j]] = [this.data[j], this.data[i]];
  }
  _parent(index) {
    return Math.floor((index - 1) / 2);
  }
  _leftChild(index) {
    return 2 * index + 1;
  }
  _rightChild(index) {
    return 2 * index + 2;
  }
  _siftUp(k) {
    // Float up, as long as the child has a higher priority than the parent, the parent switches positions, all the way up to the root node
    while (k > 0 && this.compare(this.data[k], this.data[this._parent(k)])) {
      this._swap(k, this._parent(k));
      k = this._parent(k);
    }
  }
  _siftDown(k) {
    // There is a left child
    while (this._leftChild(k) < this.size) {
      let j = this._leftChild(k);
      // There is a right child and the right child is older than the left child
      if (this._rightChild(k) < this.size &&
        this.compare(this.data[this._rightChild(k)], this.data[j])) {
        j++;
      }
      if (this.compare(this.data[k], this.data[j]))
        return;
      // The parent node is smaller than the child node
      this._swap(k, j);
      // Continue sinkingk = j; }}}Copy the code

Implementing a priority queue

With the maximum heap in place, implementing a priority queue is a piece of cake. No more nonsense, just put the code in.

class PriorityQueue {
  // Max indicates the capacity of the priority queue
  constructor(max, compare) {
    this.max = max;
    this.compare = compare;
    this.maxHeap = new MaxHeap([], compare);
  }

  getSize() {
    return this.maxHeap.getSize();
  }

  isEmpty() {
    return this.maxHeap.isEmpty();
  }

  getFront() {
    return this.maxHeap.findMax();
  }

  enqueue(e) {
    // If it is higher than the current highest priority, it is not dealt with directly
    if (this.getSize() === this.max) {
      if (this.compare(e, this.getFront())) return;
      this.dequeue();
    }
    return this.maxHeap.add(e);
  }

  dequeue() {
    if (this.getSize() === 0) return null;
    return this.maxHeap.extractMax(); }}Copy the code

So, isn’t that easy? So much for the fabled priority queue.

Hold on, one might ask: how do you ensure that this priority queue is correct?

Let’s take a test:

let pq = new PriorityQueue(3);
pq.enqueue(1);
pq.enqueue(333);
console.log(pq.dequeue());
console.log(pq.dequeue());
pq.enqueue(3);
pq.enqueue(6);
pq.enqueue(62);
console.log(pq.dequeue());
console.log(pq.dequeue());
console.log(pq.dequeue());
Copy the code

Here are the results:

333, 1, 62, 6, 3Copy the code

As you can see, the function of this priority queue meets our expectations. Later, we will use this data structure to solve the problem through practical examples.

Priority queue application

The first K high frequency elements

Given a non-empty array of integers, return the elements that occur k above the frequency of occurrence.

Example:

Input: nums = [1,1,1,2, 3], k = 2 output: [1,2]Copy the code

Description:

  • You can assume that the given k is always reasonable and that 1 is less than or equal to k is less than or equal to the number of different elements in the array.
  • Your algorithm has to be better than order n log n, where n is the size of the array.

Source: LeetCode question 347

Train of thought

The first thing you have to do is count the frequency, and then how do you pick the first K elements of the frequency that are less than order n log n?

And, of course, this is a typical example of looking at a priority queue, where you take a priority queue of capacity K and every time you kick out something that doesn’t meet the criteria, what’s left is what you get. The whole time becomes order n log K, which is obviously less than order n log n.

Since it is a priority queue, it involves how to define the priority.

If we take the high frequency as the high priority, then the first element of the queue is always the high frequency element, so every time we leave the queue, we will kick out the element with the highest frequency. If we assume that the priority queue size is K, then we will leave the K elements with the lowest frequency.

So, what we need to do is kick out the lowest frequency element every time we get out of the team, so that we’re left with the highest frequency K elements.

Should we rewrite the implementation of a small top heap in order to kick out the least frequent elements?

Absolutely not! As I said earlier, the comparative logic of the priorities can be reasonably defined. So let’s do that.

Code implementation

var topKFrequent = function(nums, k) {
   let map = {};
   let pq = new PriorityQueue(k, (a, b) => map[a] - map[b] < 0);
   for(let i = 0; i < nums.length; i++) {
       if(! map[nums[i]]) map[nums[i]] =1;
       else map[nums[i]] = map[[nums[i]]] + 1;
   }
   let arr = Array.from(new Set(nums));
   for(let i = 0; i < arr.length; i++) {
       pq.enqueue(arr[i]);
   }
   return pq.maxHeap.data;
};
Copy the code

Merge K sorted lists

Merge k sorted lists and return the merged sorted list. Analyze and describe the complexity of the algorithm.

Example:

Input: [1 - > 4 - > 5, 1 - > 3 - > 4, 2 - > 6] output: 1 - > 1 - > 2 - > 3 - > 4 - > 4 - > 5 - > 6Copy the code

This problem we have previously implemented in the linked list section, but it can also be solved perfectly with priority queues.

Question 23

/** * @param {ListNode[]} lists * @return {ListNode} */
var mergeKLists = function(lists) {
    let dummyHead = p = new ListNode();
    // Define priority function, important!
    let pq = new PriorityQueue(lists.length, (a, b) => a.val <= b.val);
    // Push the header into the priority queue
    for(let i = 0; i < lists.length; i++) 
        if(lists[i]) pq.enqueue(lists[i]);
    // Fetch the node with the smallest value. If next is not empty, push the node into the queue
    while(pq.getSize()) {
        let min = pq.dequeue();
        p.next = min;
        p = p.next;
        if(min.next) pq.enqueue(min.next);
    }
    return dummyHead.next;
};
Copy the code

How about that? Wow! Originally the priority queue can be used like this!

Dual-end queues and applications

What is a two-enqueued?

A double-endian queue is a special type of queue. Elements can be added or removed at the beginning and end of the queue. It is a kind of enhanced queue.

JS array is a typical double-ended queue. The push and pop methods add and remove elements from the tail, and the unshift and shift methods add and remove elements from the head, respectively.

Maximum sliding window

Given an array nums, a sliding window of size k moves from the leftmost part of the array to the rightmost part of the array. You can only see k numbers in the sliding window. Slide the window one bit to the right at a time.

Returns the maximum value in the sliding window.

Example:

Input: nums = [1, 3, 1, 3,5,3,6,7], and k = 3 output:,3,5,5,6,7 [3] : The position of the sliding window Maximum -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- [1] 3-1-3 3 5 6 7 3 1 [3-1-3] 3 5 6 7 3 1 3 [1-3-5] 3 6 7 5 3-1/3-3 5 6 7 5, 1, 3, 1, 5, 5, 6, 7, 6, 1, 3, 1, 5, 6, 7, 7Copy the code

Requirement: Time complexity should be linear.

Source: LeetCode problem 239

Train of thought

This is a problem that is typically solved using a two-ended queue.

Create a double-enqueued window. Each time a new value is pushed, all values smaller than it are currently in the window. Using the characteristics of the dual-end queue, you can iterate from the back to the front, and delete the small ones, otherwise stop.

This ensures that the top of the queue is always the maximum, so the time complexity of finding the maximum can be reduced to O(1). The overall time complexity is linear as more and more values in the window become obsolete.

Code implementation

The code is very concise, but if you want to write the bug free code is still quite difficult, hope you can achieve it by yourself.

var maxSlidingWindow = function(nums, k) {
    // Exception handling
    if(nums.length === 0| |! k)return [];
    let window = [], res = [];
    for(let i = 0; i < nums.length; i++) {
        // First, kick out the ones outside the sliding window
        if(window[0]! = =undefined && window[0] <= i - k) window.shift();
        // Make sure the team leader is the largest
        while(nums[window[window.length - 1]] <= nums[i])  window.pop();
        window.push(i);
        if(i >= k - 1) res.push(nums[window[0]])}return res;
};
Copy the code

Stack and queue implementations

Stack implementation queue

Use stacks to implement the following operations for queues:

Push (x) — To put an element at the end of the queue. Pop () — Removes an element from the head of the queue. Peek () – Returns the element at the head of the queue. Empty () — returns whether the queue is empty.

Example:

let queue = new MyQueue();

queue.push(1);
queue.push(2);  
queue.peek();  / / returns 1
queue.pop();   / / returns 1
queue.empty(); / / returns false
Copy the code

Problem 232

Train of thought

Since the stack is first in last out, to get the first in first out effect, we might as well use two stacks.

When we do push, we push to Stack1, and when we do pop and peek, we go through Stack2.

Of course, there is a special case, stack2 is empty, how to do pop and peek? Simply pop the elements in Stack1 and push them into Stack2, and then operate stack2 normally, as shown in the figure below:

This will ensure that it is first in, first out.

Code implementation

var MyQueue = function() {
    this.stack1 = [];
    this.stack2 = [];
};

MyQueue.prototype.push = function(x) {
    this.stack1.push(x);
};
// Move elements from Stack1 to Stack2
MyQueue.prototype.transform = function() {
  while(this.stack1.length) {
    this.stack2.push(this.stack1.pop());
  }
}

MyQueue.prototype.pop = function() {
  if(!this.stack2.length) this.transform();
  return this.stack2.pop();
};

MyQueue.prototype.peek = function() {
    if(!this.stack2.length) this.transform();
    return this.stack2[this.stack2.length - 1];
};

MyQueue.prototype.empty = function() {
    return !this.stack1.length && !this.stack2.length;
};
Copy the code

Queue implementation stack

Just the opposite of what we did in the last problem, we did it in a first-in, first-out format.

Train of thought

In the example of the queue above, push can be done directly from the end of the queue. But what about Pop and Peek?

So going back to our goal, our goal is to get the value at the end of the line, which is 3. So that’s easy to do, let’s get all the elements out of the queue, let’s just keep the elements at the end of the queue, and let’s put the rest of the elements in another queue.

Source: LeetCode Problem 225

Code implementation

During implementation, it’s worth noting that QueuE1 always holds the first element, and QueuE2 always holds the last element (that is, the top element on the stack).

But there is a catch when pushing. You cannot push the new value to queue1 when queue2 already has an element, because the top element should be updated. At this point, the new value should be pushed to Queue2, and then the old top of the stack should be pushed from QueuE2 to QueuE1, thus implementing the operation of updating the top of the stack.

var MyStack = function() {
    this.queue1 = [];
    this.queue2 = [];
};
MyStack.prototype.push = function(x) {
    if(!this.queue2.length) this.queue1.push(x);
    else {
        // Queue2 already has a value
        this.queue2.push(x);
        // The old stack top is moved to queue1
        this.queue1.push(this.queue2.shift()); }}; MyStack.prototype.transform =function() {
    while(this.queue1.length ! = =1) {
        this.queue2.push(this.queue1.shift())
    }
    // Queue2 holds the previous element
    // Switch queue1 and Queue2
    // Now queue1 contains the first elements, and queue2 contains only the last elements
    let tmp = this.queue1;
    this.queue1 = this.queue2;
    this.queue2 = tmp;
}
MyStack.prototype.pop = function() {
    if(!this.queue2.length) this.transform();
    return this.queue2.shift();
};
MyStack.prototype.top = function() {
    if(!this.queue2.length) this.transform();
    return this.queue2[0];
};
MyStack.prototype.empty = function() {
    return !this.queue1.length && !this.queue2.length;
};
Copy the code

Binary tree

Traversal of a binary tree

The former sequence traversal

Example:

Example: Input: [1, NULL,2,3] 1\2/3 Output: [1,2,3]Copy the code

Source: LeetCode question 144

recursively

/** * @param {TreeNode} root * @return {number[]} */
var preorderTraversal = function(root) {
    let arr = [];
    let traverse = (root) = > {
      if(root == null) return;
      arr.push(root.val);
      traverse(root.left);
      traverse(root.right); 
    }
    traverse(root);
    return arr;
};
Copy the code

Non-recursive

var preorderTraversal = function(root) {
    if(root == null) return [];
    let stack = [], res = [];
    stack.push(root);
    while(stack.length) {
        let node = stack.pop();
        res.push(node.val);
        // The left child is last in, first out, and the depth first traversal is left, then right
        if(node.right) stack.push(node.right);
        if(node.left) stack.push(node.left);
    }
    return res;
};
Copy the code

In the sequence traversal

Given a binary tree, return the in-order traversal of it.

Example:

Input: [1, NULL,2,3] 1\2/3 Output: [1,3,2]Copy the code

Question 94

Recursion:

/** * @param {TreeNode} root * @return {number[]} */
var inorderTraversal = function(root) {
    let arr = [];
    let traverse = (root) = > {
      if(root == null) return;
      traverse(root.left);
      arr.push(root.val);
      traverse(root.right); 
    }
    traverse(root);
    return arr;
};
Copy the code

Non-recursive

var inorderTraversal = function(root) {
    if(root == null) return [];
    let stack = [], res = [];
    let p = root;
    while(stack.length || p) {
        while(p) {
            stack.push(p);
            p = p.left;
        }
        let node = stack.pop();
        res.push(node.val);
        p = node.right;
    }   
    return res;
};
Copy the code

After the sequence traversal

Given a binary tree, return a sequential traversal of it.

Example:

Input: [1, NULL,2,3] 1\2/3 Output: [3,2,1]Copy the code

Problem 145

recursively

/** * @param {TreeNode} root * @return {number[]} */
var postorderTraversal = function(root) {
    let arr = [];
    let traverse = (root) = > {
      if(root == null) return;
      traverse(root.left);
      traverse(root.right);
      arr.push(root.val);
    }
    traverse(root);
    return arr
};
Copy the code

Non-recursive

var postorderTraversal = function(root) {
    if(root == null) return [];
    let stack = [], res = [];
    let visited = new Set(a);let p = root;
    while(stack.length || p) {
        while(p) {
            stack.push(p);
            p = p.left;
        }
        let node = stack[stack.length - 1];
        // If the right child exists and the right child has not been accessed
        if(node.right && ! visited.has(node.right)) { p = node.right; visited.add(node.right); }else{ res.push(node.val); stack.pop(); }}return res;
};
Copy the code

Maximum/minimum depth

Maximum depth

Given a binary tree, find its maximum depth.

The depth of a binary tree is the number of nodes along the longest path from the root node to the farthest leaf node.

Note: A leaf node is a node with no child nodes.

Example: given a binary tree [3,9,20,null,null,15,7] :

    3
   / \
  9  20
    /  \
   15   7
Copy the code

Returns its maximum depth of 3. Source: LeetCode Problem 104

The recursive implementation

The implementation is very simple, just post the code:

/** * @param {TreeNode} root * @return {number} */
var maxDepth = function(root) {
    // Recursive termination condition
    if(root == null) return 0;
    return Math.max(maxDepth(root.left) + 1, maxDepth(root.right) + 1);
};
Copy the code

Non-recursive implementation

It’s very easy to understand the sequence traversal.

var maxDepth = function(root) {
    if(root == null) return 0;
    let queue = [root];
    let level = 0;
    while(queue.length) {
        let size = queue.length;
        while(size --) {
            let front = queue.shift();
            if(front.left) queue.push(front.left);
            if(front.right) queue.push(front.right);
        }
        // The value after level ++ indicates how many layers of nodes have been processed
        level ++;
    }
    return level;
};
Copy the code

The minimum depth

Given a binary tree, find its minimum depth.

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

Note: A leaf node is a node with no child nodes.

Example:

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

    3
   / \
  9  20
    /  \
   15   7
Copy the code

Return its minimum depth of 2.

Question 111

The recursive implementation

In the process of implementation, there is a pitfall if you follow the maximum depth approach, namely:

/** * @param {TreeNode} root * @return {number} */
var minDepth = function(root) {
    // Recursive termination condition
    if(root == null) return 0;
    return Math.min(minDepth(root.left) + 1, minDepth(root.right)+1);
};
Copy the code

When the root node has an empty child, it returns 1, but this is incorrect. The minimum height refers to the smallest path from the root node to the nearest leaf node, not to an empty node.

Therefore, we need to make the following adjustments:

var minDepth = function(root) {
    if(root == null) return 0;
    // If the left and right children are not empty, call it like this
    if(root.left && root.right)
        return Math.min(minDepth(root.left), minDepth(root.right)) + 1;
    // The right child is empty, ignore it directly
    else if(root.left)
        return minDepth(root.left) + 1;
    // Left child is empty, ignore
    else if(root.right)
        return minDepth(root.right) + 1;
    // If both children are empty, the leaf node is reached. Return 1
    else return 1;
};
Copy the code

Then the program will work.

Non-recursive implementation

Similar to the maximum height problem, the sequential traversal method is adopted, which is easy to understand.

var minDepth = function(root) {
    if(root == null) return 0;
    let queue = [root];
    let level = 0;
    while(queue.length) {
        let size = queue.length;
        while(size --) {
            let front = queue.shift();
            // Find the leaf node
            if(! front.left && ! front.right)return level + 1;
            if(front.left) queue.push(front.left);
            if(front.right) queue.push(front.right);
        }
        // The value after level ++ indicates how many layers of nodes have been processed
        level ++;
    }
    return level;
};
Copy the code

Symmetric binary tree

Given a binary tree, check whether it is mirror symmetric.

For example, a binary tree [1,2,2,3,4,4,3] is symmetric.

    1
   / \
  2   2
 / \ / \
3  4 4  3
Copy the code

But the following [1,2,2,null,3,null,3] is not mirror symmetric:

1 / \ 2 2\3 3Copy the code

Source: LeetCode 101

The recursive implementation

Recursion code is very dry and elegant, hope you first implement it yourself, and then compare and improve.

/** * @param {TreeNode} root * @return {boolean} */
var isSymmetric = function(root) {
    let help = (node1, node2) = > {
        / / is empty
        if(! node1 && ! node2)return true;
        // One is null and the other is not null, or the values of the two nodes are not equal
        if(! node1 || ! node2 || node1.val ! == node2.val)return false;
        return help(node1.left, node2.right) && help(node1.right, node2.left);
    }
    if(root == null) return true;
    return help(root.left, root.right);
};
Copy the code

Non-recursive implementation

A queue is used to save the visited nodes, and two nodes are retrieved at a time for comparison.

var isSymmetric = function(root) {
    if(root == null) return true;
    let queue = [root.left, root.right];
    let node1, node2;
    while(queue.length) {
        node1 = queue.shift();
        node2 = queue.shift();
        // Both nodes are empty
        if(! node1 && ! node2)continue;
        // One is null and the other is not null, or the values of the two nodes are not equal
        if(! node1 || ! node2 || node1.val ! == node2.val)return false;
        queue.push(node1.left);
        queue.push(node2.right);
        queue.push(node1.right);
        queue.push(node2.left);
    }
    return true;
};
Copy the code

LCA problem

Inflationary Common Ancestor (LCA)

The definition of recent public ancestor in Baidu Encyclopedia is: “For two nodes P and Q of the root tree T, the recent public ancestor is expressed as a node X, where x is the ancestor of P and Q and the depth of X is as large as possible (a node can also be its own ancestor).

The nearest common ancestor of a binary tree

For a simple binary tree: root = [3,5,1,6,2,0,8, null, null, 7, 4]

Input: root = [3,5,1,6,2,0,8, null, null, 7, 4], p = 5, output q = 1:3: node 5 and 1 recent common ancestor node 3.Copy the code

Source: LeetCode Problem 236

Thought analysis

Idea 1: First go through the binary tree, record the parent node of each node. And then, for the node p that they gave us, we keep looking for the upper nodes of P, all the way down to the root, and record the set of the upper nodes of P. Then, for the q node, keep looking for its upper node according to the record. In the process of searching, once the upper node is found to be included in the collection just now, it means that the latest common ancestor is found, and it directly returns.

Idea 2: If the current node is p or Q, return to that node. Otherwise, look for left and right children. If the left child does not contain P or Q, look for the right child, if the right child does not contain P or Q, look for the left child. The remaining case is that both the left and right children have a P or a Q, so we just return this node.

The ancestor node set is valid

/** * @param {TreeNode} root * @param {TreeNode} p * @param {TreeNode} q * @return {TreeNode} */
var lowestCommonAncestor = function(root, p, q) {
    if(root == null || root == p || root == q) return root;
    let set = new Set(a);let map = new WeakMap(a);let queue = [];
    queue.push(root);
    // Sequence traversal
    while(queue.length) {
        let size = queue.length;
        while(size --) {
            let front = queue.shift();
            if(front.left) {
                queue.push(front.left);
                // Record the parent node
                map.set(front.left, front);
            }
            if(front.right) {
                queue.push(front.right);
                // Record the parent nodemap.set(front.right, front); }}}// Construct the upper node set of P
    while(p) {
        set.add(p);
        p = map.get(p);
    }
    while(q) {
        // If the common nodes overlap, return directly
        if(set.has(q))returnq; q = map.get(q); }};Copy the code

So you can see that the entire binary tree has been traversed, and the time complexity is about order n, but the space complexity is high because of the hash table, so we’re going to use another way of traversing, which reduces the space cost a lot.

Depth first traversal

The code is very simple and beautiful, but it’s more important to understand the recursive invocation process, the code is executed from the top down, I recommend you to understand it from the bottom up, from the bottom left node, I believe you will understand the whole process.

var lowestCommonAncestor = function(root, p, q) {
    if (root == null || root == p || root == q) return root;
    let left = lowestCommonAncestor(root.left, p, q);
    let right = lowestCommonAncestor(root.right, p, q);
    if(left == null) return right;
    else if(right == null) return left;
    return root;
};
Copy the code

The nearest common ancestor of a binary search tree

Given the following binary search tree: root = [6,2,8,0,4,7,9, null, null, 3, 5)

Input: root = [6,2,8,0,4,7,9, null, null, 3, 5), p = 2, q = 8 output: 6: node 2 and 8 of recent common ancestor is 6.Copy the code

Source: LeetCode problem 235

implementation

Binary search tree as a special binary tree, of course, can be used in the above two ways to achieve.

However, with the ordered nature of binary search trees, we can also write another version of deeply optimized traversal.

/** * @param {TreeNode} root * @param {TreeNode} p * @param {TreeNode} q * @return {TreeNode} */
var lowestCommonAncestor = function(root, p, q) {
    if(root == null || root == p || root == q) return root;
    // root. Val is bigger than p and q
    if(root.val > p.val && root.val > q.val) 
        return lowestCommonAncestor(root.left, p, q);
    // root. Val is smaller than p and q
    if(root.val < p.val && root.val < q.val) 
        return lowestCommonAncestor(root.right, p, q);
    else 
        return root;
};
Copy the code

We can also use a non-recursive approach:

var lowestCommonAncestor = function(root, p, q) {
    let node = root;
    while(node) {
        if(p.val > node.val && q.val > node.val)
            node = node.right;
        else if(p.val < node.val && q.val < node.val) 
            node = node.left;
        else returnnode; }};Copy the code

Are you impressed by the simplicity and elegance of binary trees? I hope you can enjoy the process of traversal, and then be sure to implement it yourself, to ensure that the data structure is familiar enough to enhance their own programming internal forces.

The path problem in binary trees

No.1 Diameter of a binary tree

Given a binary tree, you need to calculate its diameter. The diameter length of a binary tree is the maximum of the path length of any two nodes. This path might go through the root.

Example: Given a binary tree

          1
         / \
        2   3
       / \     
      4   5  
Copy the code

Return 3, whose length is either path [4,2,1,3] or [5,2,1,3].

Note: The length of the path between two nodes is expressed by the number of edges between them.

Source: LeetCode question 543

Train of thought

The diameter is essentially the maximum sum of the heights of the left and right subtrees of a node in a tree.

Notice that I’m talking about nodes in the tree, not the root node. Because here’s the thing:

1/2 / \ 4 5 / \ 8 6\7Copy the code

At this time, the path with the largest diameter is 8 -> 4 -> 2-> 5 -> 6 -> 7. The interface element is not the root node. This is where the problem needs special attention, otherwise there is no solution.

A preliminary solution

The goal has been determined, to find the maximum value of the height sum of the left and right subtrees of the node in the tree. Open dry!

/** * @param {TreeNode} root * @return {number} */
var diameterOfBinaryTree = function(root) {
    // Find the maximum depth
    let maxDepth = (node) = > {
      if(node == null) return 0;
      return Math.max(maxDepth(node.left) + 1, maxDepth(node.right) + 1);
    }
    let help = (node) = > {
        if(node == null) return 0;
        let rootSum = maxDepth(node.left) + maxDepth(node.right);
        let childSum = Math.max(help(node.left), help(node.right));
        return Math.max(rootSum, childSum);
    }
    if(root == null) return 0;
    return help(root);
};
Copy the code

Such a piece of code can be passed in LeetCode, but the time is not very satisfactory, why?

Because the repeated calls to maxDepth add a lot of unnecessary access to some of the nodes in the tree. Such as:

1/2 / \ 4 5 / \ 8 6\7Copy the code

Let’s see when node 8 is accessed, maxDepth(node 2) is accessed, and maxDepth(node 4) is accessed again. If the node is higher in the hierarchy, it will be accessed more frequently. The same is true for the remaining nodes 6 and 7. The number of visits per node is approximately O(logK)(if the current node is at layer K). So can we get this frequency down to order one?

And the answer is yes, so let’s optimize this algorithm.

Optimization method

var diameterOfBinaryTree = function(root) {
    let help = (node) = > {
        if(node == null) return 0;
        let left = node.left ? help(node.left) + 1: 0;
        let right = node.right ? help(node.right) + 1: 0;
        let cur = left + right;
        if(cur > max) max = cur; 
        // The return operation is critical
        return Math.max(left, right);
    }
    let max = 0;
    if(root == null) return 0;
    help(root);
    return max;
};
Copy the code

In this process, a global variable Max is set, and the tree is traversed with depth first. Every time a node is traversed, Max is updated, and the maximum height of the left and right subtrees of the current node is passed to the parent function from the bottom up by returning the value, so that each node can be accessed only once.

Now we submit our optimized code, and the time consumption is significantly reduced.

All paths of binary tree No.2

Given a binary tree, return all paths from the root node to the leaf node.

Note: A leaf node is a node with no child nodes.

Example:

Input: 1 / \ 2 3\5 Output: ["1 - > 2 - > 5"."1 - > 3"]
Copy the code

Explanation: The paths of all root nodes to leaf nodes are: 1->2->5, 1->3

Source: LeetCode question 257

The recursive method

Traversal is performed using DFS(depth-first traversal).

/** * @param {TreeNode} root * @return {string[]} */
var binaryTreePaths = function(root) {
    let path = [];
    let res = [];
    let dfs = (node) = > {
        if(node == null) return;
        path.push(node);
        dfs(node.left);
        dfs(node.right);
        if(! node.left && ! node.right) res.push(path.map(item= > item.val).join('- >'));
        // Delete a node from the path after it has been accessed
        path.pop();
    }
    dfs(root);
    return res;
};
Copy the code

Non-recursive solution

Let’s do it in a non-recursive way, just to review the implementation of a post-ordered traversal. Post-order traversal is also a concrete implementation of DFS. : : :

var binaryTreePaths = function(root) {
    if(root == null) return [];
    let stack = [];
    let p = root;
    let set = new Set(a); res = [];while(stack.length || p) {
        while(p) {
            stack.push(p);
            p = p.left;
        }
        let node = stack[stack.length - 1];
        // Leaf node
        if(! node.right && ! node.left) { res.push(stack.map(item= > item.val).join('- >'));
        }
        // The right child exists and the right child has not been accessed
        if(node.right && ! set.has(node.right)) { p = node.right; set.add(node.right); }else{ stack.pop(); }}return res;
};
Copy the code

No.3 Maximum path sum of a binary tree

Given a non-empty binary tree, return its maximum path sum.

In this case, a path is defined as a sequence from any node in the tree to any node. The path contains at least one node and does not necessarily pass through the root node.

Example:

Input: [-10,9,20, NULL, NULL,15,7] -10 / \ 9 20 / \ 15 7 Output: 42Copy the code

Question 124

Recursive solution

/** * @param {TreeNode} root * @return {number} */
var maxPathSum = function(root) {
    let help = (node) = > {
        if(node == null) return 0;
        let left = Math.max(help(node.left), 0);
        let right = Math.max(help(node.right), 0);
        let cur = left + node.val + right;
        // If the value of the path on a node is larger than Max, update Max
        if(cur > max) max = cur;
        // There is no turning point between right and left
        return Math.max(left, right) + node.val;
    }
    let max = Number.MIN_SAFE_INTEGER;
    help(root);
    return max;
};
Copy the code

Binary search tree

No.1 verify binary search trees

Given a binary tree, judge whether it is a valid binary search tree.

Suppose a binary search tree has the following characteristics:

The left subtree of a node contains only the number smaller than the current node. The right subtree of a node contains only the number greater than the current node. All left and right subtrees must themselves also be binary search trees.

Example 1:

Input: 2 / \ 1 3 Output:true
Copy the code

Question 98

Method 1: in order traversal

The value of the previous node is saved through the middle order traversal. When the current node is scanned, it is compared with the value of the previous node. If the value is greater than the previous node, the condition is met; otherwise, it is not a binary search tree.

/** * @param {TreeNode} root * @return {boolean} */
var isValidBST = function(root) {
    let prev = null;
    const help = (node) = > {
        if(node == null) return true;
        if(! help(node.left))return false;
        if(prev ! = =null && prev >= node.val) return false;
        // Save the current node in preparation for the next node traversal
        prev = node.val;
        return help(node.right);
    }
    return help(root);
};
Copy the code

Method two: DFS with upper and lower bounds

Binary search tree each node value, there is a upper and lower bounds, the process of depth-first traversal, if access to the left child, by the value of the current node to update the upper bound of the left child node, access to the right child at the same time, update the right child’s lower bound, as long as a node value of crossing the line, does not meet the conditions of the binary search tree.

  parent
  /    \
left   right
Copy the code

If this is part of a large binary tree (parent, left, and right are all actual nodes), then the order of all nodes must look like this:

. left, parent, right…

You can see that the strictest upper bound for the left child is this node, and the strictest lower bound for the right child is also this node. We update the upper and lower bounds according to this rule.

Recursive implementation:

var isValidBST = function(root) {
    const help = (node, max, min) = > {
        if(node == null) return true;
        if(node.val >= max || node.val <= min) return false;
        // The left child updates the upper bound, and the right child updates the lower bound
        return help(node.left, node.val, min)
                && help(node.right, max, node.val);
    }
    return help(root, Number.MAX_SAFE_INTEGER, Number.MIN_SAFE_INTEGER);
};
Copy the code

Non-recursive implementation:

var isValidBST = function(root) {
    if(root == null) return true;
    let stack = [root];
    let min = Number.MIN_SAFE_INTEGER;
    let max = Number.MAX_SAFE_INTEGER;
    root.max = max; root.min = min;
    while(stack.length) {
        let node = stack.pop();
        if(node.val <= node.min || node.val >= node.max)
            return false;
        if(node.left) {
            stack.push(node.left);
            // Update the upper and lower bounds
            node.left.max = node.val;
            node.left.min = node.min;
        }
        if(node.right) {
            stack.push(node.right);
            // Update the upper and lower boundsnode.right.max = node.max; node.right.min = node.val; }}return true;
};
Copy the code

No.2 converts an ordered array to a binary search tree

Convert an ordered array in ascending order to a highly balanced binary search tree.

In this case, a height-balanced binary tree is a binary tree where the absolute value of the height difference between the left and right subtrees of each node is less than 1.

Example:

Given an ordered array: [-10,-3,0,5,9], one possible answer is: [0,-3,9,-10, NULL,5], which can represent the following highly balanced binary search tree: 0 / \ -3 9 / / -10 5Copy the code

Source: LeetCode problem 108

The recursive implementation

/** * @param {number[]} nums * @return {TreeNode} */
var sortedArrayToBST = function(nums) {
    let help = (start, end) = > {
        if(start > end) return null;
        if(start === end) return new TreeNode(nums[start]);
        let mid = Math.floor((start + end) / 2);
        // Find the midpoint to create a node
        let node = new TreeNode(nums[mid]);
        node.left = help(start, mid - 1);
        node.right = help(mid + 1, end);
        return node;
    }
    return help(0, nums.length - 1);
};
Copy the code

Recursive programs are easy to understand, constantly calling help to complete the building of the whole tree. So how do we solve it with non-recursion? I think this is a question worth thinking about. I hope you can try it out, and if you really can’t think of it, you can refer to the non-recursive version I wrote below.

The idea is the same as the recursive version, but it is implemented using a stack to achieve the effect of DFS.

/** * @param {number[]} nums * @return {TreeNode} */
var sortedArrayToBST = function(nums) {
    if(nums.length === 0) return null;
    let mid = Math.floor(nums.length / 2);
    let root = new TreeNode(nums[mid]);
    // Note: 1. Index refers to the index of the current element in the array
    // 2. The value of each node is the midpoint of the interval, so the start attribute is the beginning of the interval, and the end attribute is the end
    root.index = mid; root.start = 0; root.end = nums.length - 1;
    let stack = [root];
    while(stack.length) {
        let node = stack.pop();
        // Node comes out, which itself contains a range, [start,... index,... end]
        [node.start, node.index-1]
        if(node.index - 1 >= node.start) {
            let leftMid = Math.floor((node.start + node.index)/2);
            let leftNode = new TreeNode(nums[leftMid]);
            node.left = leftNode;
            // Initialize the interval start, end, and index of the new node
            leftNode.start = node.start;
            leftNode.end = node.index - 1;
            leftNode.index = leftMid;
            stack.push(leftNode);
        }
        // The node. Index is in the middle of the node
        [node.index + 1, node.end
        if(node.end >= node.index + 1) {
            let rightMid = Math.floor((node.index + 1 + node.end)/2);
            let rightNode = new TreeNode(nums[rightMid]);
            node.right = rightNode;
            // Initialize the interval start, end, and index of the new node
            rightNode.start = node.index + 1; rightNode.end = node.end; rightNode.index = rightMid; stack.push(rightNode); }}return root;
};
Copy the code

The No.3 binary tree is expanded to a linked list

Given a binary (search) tree, expand it in place to a linked list.

For example, given a binary tree

1 / \ 2 5 / \ 3 4 6Copy the code

Expand it to:

1\2\3\4\5\6Copy the code

Source: LeetCode 114

recursively

Use the post-order traversal, traversal left and right children what do we do? Use the following image to illustrate (click to enlarge) :

/** * @param {TreeNode} root * @return {void} Do not return anything, modify root in-place instead. */
var flatten = function(root) {
    if(root == null) return;
    flatten(root.left);
    flatten(root.right);
    if(root.left) {
        let p = root.left;
        while(p.right) {
            p = p.right;
        }
        p.right = root.right;
        root.right = root.left;
        root.left = null; }};Copy the code

Non-recursive

Use a non-recursive sequential traversal, the same idea as before.

var flatten = function(root) {
    if(root == null) return;
    let stack = [];
    let visited = new Set(a);let p = root;
    // Start the sequential traversal
    while(stack.length || p) {
        while(p) {
            stack.push(p);
            p = p.left;
        }
        let node = stack[stack.length - 1];
        // If the right child exists and the right child has not been accessed
        if(node.right && ! visited.has(node.right)) { p = node.right; visited.add(node.right); }else {
            // The following is the key logic in the diagram
            if(node.left) {
                let p = node.left;
                while(p.right) {
                    p = p.right;
                }
                p.right = node.right;
                node.right = node.left;
                node.left = null; } stack.pop(); }}};Copy the code

No.4 Different binary search tree II

Given an integer n, generate all items made up of 1… N is a binary search tree composed of nodes.

Example:

Input: 3 output: [[1, null, 3, 2], [3, 2, null, 1], [3, 1, null, null, 2], [2,1,3], [1, null, 2, null, 3]] : The above output corresponds to the following five binary search trees of different structures: 1 3 3 2 1 \ / / / \ 3 2 1 1 3 2 / / \ 2 1 2 3Copy the code

Problem 95

The recursive method

Create a subtree recursively

/** * @param {number} n * @return {TreeNode[]} */
var generateTrees = function(n) {
    let help = (start, end) = > {
        if(start > end) return [null];
        if(start === end) return [new TreeNode(start)];
        let res = [];
        for(let i = start; i <= end; i++) {
            // Left child set
            let leftNodes = help(start, i - 1);
            // Right child set
            let rightNodes = help(i + 1, end);
            for(let j = 0; j < leftNodes.length; j++) {
                for(let k = 0; k < rightNodes.length; k++) {
                    let root = newTreeNode(i); root.left = leftNodes[j]; root.right = rightNodes[k]; res.push(root); }}}return res;
    }
    if(n == 0) return [];
    return help(1, n);
};
Copy the code

Non-recursive solution

var generateTrees = function(n) {
    let clone = (node, offset) = > {
        if(node == null) return null;
        let newnode = new TreeNode(node.val + offset);
        newnode.left = clone(node.left, offset);
        newnode.right = clone(node.right, offset);
        return newnode;
    }
    if(n == 0) return [];
    let dp = [];
    dp[0] = [null];
    // I is the number of nodes in the subproblem: [1], [1,2], [1,2,3]... Increments until [1,2,3... n]
    for(let i = 1; i <= n; i++) {
        dp[i] = [];
        for(let j = 1; j <= i; j++) {
            // Left subtree set
            for(let leftNode of dp[j - 1]) {
                // Right subtree set
                for(let rightNode of dp[i - j]) {
                    let node = new TreeNode(j);
                    // The left subtree is shared
                    node.left = leftNode;
                    // The right subtree cannot be shared, but you can borrow a tree with the same number of nodes, adding an offset for each nodenode.right = clone(rightNode, j); dp[i].push(node); }}}}return dp[n];
};
Copy the code

So much for sharing this time. You can see how much knowledge there is about data structures and algorithms, but with the help of this series, I’m sure you’ll be able to get your hands on data structures and algorithms. Here’s the Github repository for this series.

Making the warehouse

Online reading address for this series

In addition, my blog has been sorted out now, the address is here, click open.