头图

Hello, brave friends, hello everyone, I am your little five, the king of the mouth, I am in good health, and my brain is not sick.

I have a wealth of hair loss skills, which can make you a veteran celebrity.

It is my main theme that I will write it when I look at it, and it is my characteristic to pick my feet. There is a trace of strength in the humble. The stupid blessing of a fool is the greatest comfort to me.

Welcome to fifth of algorithm Series stacks and queues.

Preface

This series of articles is based on the two books "An Illustrated Algorithm" and "Learning JavaScript Algorithms" as the core, and the rest of the materials are supplementary, accompanied by the author's humble opinion. Strive to lead everyone to appreciate the wonders of this algorithmic world in simple and interesting language.

The content of this article is stacks and queues. The author will lead you to js , and explore the application of stacks and queues in actual scenarios. In the meantime, the program call stack and the task queue will be interspersed with explanations, making it easy to understand program execution, recursion, and asynchronous calls. Finally, attach a few exercises to deepen understanding and consolidate what you have learned.

Stack

Features and realization

👺 Features: < last in, first out >

👇 The badminton tube in the picture below is an image metaphor for the stack

👺 create stack

👇 Next we use js to simulate a stack and add the following methods to it:

  • push(element(s)) : Push into the stack -> add one or more elements to the top of the stack
  • pop() : Pop -> remove the top element of the stack, and return the removed element
  • peek() : return to the top element of the stack
  • isEmpty() : Whether there are elements in the stack
  • clear() : Remove all elements in the stack
  • size() : the number of elements in the stack

Application: Decimal to Binary

🤔 Thinking: How to convert decimal to binary

< divide by 2 and take the remainder, arrange reverse order>: Divide the decimal number by 2 and arrange the remainder in reverse order after taking the remainder. The result is the binary of the number.

The above analysis shows that this is a typical last-in first-out scenario, that is, a stack scenario. The code is as follows:

import Stack from 'stack';
export const binaryConversion = (num: number) => {
  const stack = new Stack<number>();
  let binaryStr = '';

  while (num > 0) {
    stack.push(num % 2);
    num = Math.floor(num / 2);
  }

  while (!stack.isEmpty()) {
    binaryStr += stack.pop();
  }

  return binaryStr;
}

// 输入 10 -> 输出: '1010' 

👺 Extension: Decimal to other bases

Analysis of thinking: we add an input parameter base to represent the base; binary remainder is 0 ~ 1, octal remainder is 0 ~ 7, hexadecimal remainder is 0 ~ 15, and letters are represented by 0 ~ 9, A ~ F.

👉 Just add a set of mappings to numbers, and you're done!

import Stack from 'stack';
export const binaryConversion = (num: number, base: number) => {
  const stack = new Stack<number>();
  let binaryStr = '';
+ let baseMap = '0123456789ABCDEF';

  while (num > 0) {
-   stack.push(num % 2);
-   num = Math.floor(num / 2);
+   stack.push(num % base);
+   num = Math.floor(num / base);
  }

  while (!stack.isEmpty()) {
-   binaryStr += stack.pop();
+   binaryStr += baseMap[stack.pop() as number];
  }

  return binaryStr;
}

// binaryConversion(100345, 2)  ->  11000011111111001
// binaryConversion(100345, 8)  ->  303771
// binaryConversion(100345, 16) ->  187F9

Function call stack-an important artifact to understand recursion

When it comes to recursion, everyone will definitely say, I will do it, isn't it a process of continuously looping itself?

But do you really understand recursion? Can fast sorting, merging, traversing the first, middle, and last of the tree easily? If not, the recursion is still not fully understood, that is, the order of execution of the function is not understood.

Let's look at the following function:

const greet = (name) => {
  console.log(`hello, ${name}!`);
  newGreet(name);
  console.log('getting ready to say bye...');
  bye();
}

const newGreet = (name) => {
  console.log(`how are you, ${name}?`);
}

const bye = () => {
  console.log('ok, bye!');
}

greet('xiaowu');

What is its execution process? Let's take a look together👇

Let's write a simple recursion to find the factorial of 5 ($5!$)

const fact = num => {
  if (num === 1) return 1;
  return num * fact(num - 1);
}
fact(5);

Cuihua, top executive picture

👺 Extension: execution context

Divided into "global execution context" and "function execution context"

Before starting to execute any code, JavaScript will create a global context and push it to the bottom of the stack, which is the window object; whenever we call a function, a new function execution context will be created, as shown in the process diagram above; the entire JavaScript execution The process is a "call stack" .

queue

Features and realization

👺 Features: < FIFO >

👇 First-come, first-served is a metaphor for the image of a queue

👺 create queue

👇 Next we use js to simulate a queue and add the following method to it:

  • enqueue(element(s)) : Join the team -> add one or more elements to the end of the team
  • dequeue() : Dequeue -> remove the element at the head of the team, and return the removed element
  • peek() : Return to the top element of the team
  • isEmpty() : Whether there are any elements in this queue
  • size() : the number of elements in the queue

Priority queue

In real life, the principle of first-come, first-served is not always followed; for example, in hospitals, doctors will give priority to patients with more serious illnesses; this leads to the first variant of the queue-the priority queue;

Priority queue-When inserting data, insert the data in the correct position according to its weight.

👺 code analysis

  • Add an input parameter representing the weight to determine the insertion position
  • Rewrite the enqueue method so that it can be inserted into the correct position according to the weight
  • The Queue class private to protected class after the inheritance rewritable enqueue method
  • When the queue is empty, directly push element
  • When the queue is not empty, determine the element insertion position, if it is the end of the push , directly 06111f2cabd305, otherwise use splice insert

👺 code implementation

Circular queue

" Drumming Pass Flower " is a rule of the game. People pass flowers during drumming. When the drum beat stops, whoever passes the flowers to the hands will be eliminated until one person is left (the winner).

This game has been in a looping state, which leads to the second variant of the queue-the circular queue.

We plan to eliminate one person every n times, and n is the participation.

👺 code analysis

  • How to loop? first and is listed first
  • When the queue is greater than 1, open the loop until there is only one left, that is, the winner

👺 code implementation

JavaScript task queue

In the previous book, we talked about the program call stack, and the program does not all execute synchronously. What happens when it executes asynchronous operations?

🤔 Thinking: $setTimeout(() => {execute function}, 0)$ Why is it not executed immediately

[Image source-Detailed explanation of JavaScript Event Loop mechanism and practical application in

The program is executed sequentially and pushed into the stack in turn; when an asynchronous task is encountered, it is pushed into the task queue; after the program stack is emptied, the task queue is read and pushed into the stack respectively; this repeated process is Event Loop- The event loop is .

🦅 Here is an article explaining the JS execution mechanism This time, I thoroughly understand the JavaScript execution mechanism

Small scale chopper

The following questions are all from LeetCode . The author will provide a solution idea for each question; this idea is by no means the best, and you are welcome to think positively and leave your own unique opinions.

LeetCode 232. Use stack to implement queue

👺 Title description

Please use only two stacks to implement a first-in first-out queue. The queue should support all operations supported by the general queue ( enqueue , dequeue , peek , size , isEmpty );

You can only use standard stack operations~ that is, only push , pop , peek , size and isEmpty operations are legal.

👺 title analysis

There are two ways of thinking about stack implementation queue, namely, starting from stacking and starting from stacking. The author chooses to start from stacking;

The core idea is how to make the elements pushed into the stack later at the bottom of the stack and the elements pushed into the stack first at the top of the stack;

Use the auxiliary stack to reverse the order to simulate the queue, the process is as follows 👇

👺 code implementation

class Queue<T> {
  private items = new Stack<T>();

  enqueue(item: T) {
    if (this.items.isEmpty()) {
      this.items.push(item);
      return;
    }

    const _stack = new Stack<T>();
    while (!this.items.isEmpty()) {
      _stack.push(this.items.pop() as T);
    }
    _stack.push(item);

    while (!_stack.isEmpty()) {
      this.items.push(_stack.pop() as T);
    }
  }
  
 // 其余方法无变化
}

LeetCode 225. Use queues to implement stack

👺 Title description

Please use only two queues to implement a last-in first-out (LIFO) stack, and support all operations of the ordinary stack ( push , pop , peek , size , isEmpty );

You can only use the basic operations of the queue~ that is, enqueue , dequeue , peek , size and isEmpty .

👺 title analysis

Queue implementation stack, there are also two ways of thinking, starting from entering the team and starting from the team leaving, the author chooses to start from the team;

The core idea is how to make the elements that enter the team at the head of the team, and the elements that enter the team first at the end of the team;

Every time a new element is added to the team, the other elements in the queue can be dequeued first and then entered, the process is as follows 👇

👺 code implementation

class Stack<T> {
  private items = new Queue<T>();

  push(item: T) {
    this.items.enqueue(item);
    for (let i = 1; i < this.items.size(); i++) {
      this.items.enqueue(this.items.dequeue() as T);
    }
  }

  // 其余方法无变化
}

LeetCode 20. Valid brackets

👺 Title description

👺 title analysis

Let's look at the following 🌰 example

{[()][]}

Whether the given string is closed or not is to find the smallest closed unit. After removing it, continue to search until the string is empty to meet the condition, the process is as follows 👇

  • The analysis as shown in the figure above is completely consistent with the idea of the stack
  • In case of opening parenthesis, push to stack
  • In case of right parenthesis: if it is the smallest closed unit, it will pop out of the stack, if it does not match, the string will not be closed
  • After the operation is completed, if the stack is empty, the string is closed

👺 code implementation

const isValid = (str: string) => {
  const stack = new Stack();
  const strMap = {
    ')': '(',
    ']': '[',
    '}': '{',
  };
  for (let i of str) {
    if (['(', '[', '{'].includes(i)) {
      stack.push(i);
    }
    if (strMap[i]) {
      if (strMap[i] === stack.peek()) {
        stack.pop();
      } else {
        return false;
      }
    }
  }
  return stack.size() === 0;
}

LeetCode 32. The longest valid bracket

👺 Title description

👺 title analysis

This question is an extension of the previous question. From the analysis of the previous question, we can know that this question should continue to use the stack;

❓ Then how to find the length? If it is linked to a number, use the subscript of the array, and the subscript difference is the length

The above figure shows that the length is the pop index minus the top value of the stack ; in actual operation, the right parenthesis is not put on the stack, so we record a bottom value; if the stack is empty, the bottom is subtracted, and the stack is not empty, then Subtract the top value of the stack.

👺 code implementation

const longestValidParentheses = (str: string) => {
  const stack = new Stack<number>();
  let maxLen = 0;
  let bottom: number | undefined = undefined;
  for (let i = 0; i < str.length; i++) {
    if (stack.isEmpty() && str[i] === ')') bottom = undefined;

    if (str[i] === '(') {
      if (bottom === undefined) bottom = i - 1;
      stack.push(i);
    }

    if (!stack.isEmpty() && str[i] === ')') {
      stack.pop();
      let len = i - (stack.peek() ?? bottom);
      if (len > maxLen) maxLen = len;
    }
  }
  return maxLen;
}

LeetCode 239. Maximum sliding window

👺 title description

👺 title analysis

[1, 3, -1, -3, 5, 3, 6, 7] , isn’t this just a walking queue? The execution order of 06111f2cabdafe is as follows 👇

So the author implemented the following code:

const maxSlidingWindow = (nums: number[], k: number) => {
  let queue: number[] = [];
  let maxArr: number[] = [];
  nums.forEach(item => {
    if (queue.length <= k) {
      queue.push(item);
    }
    if (queue.length === k) {
      let max = queue[0];
      for (let i = 1; i < queue.length; i++) {
        if (queue[i] > max) max = queue[i];
      }
      maxArr.push(max);
      queue.shift();
    }
  })
  return maxArr;
}

When I submitted to LeetCode with enthusiasm

Helpless, let’s continue to optimize and see how we can reduce the loop; observing the picture above, we find that the number on the left of the maximum value in the window is meaningless, and we don’t need to care about it and can dequeue it, so that the maximum value of the window is the first element ;

❓ When will the head of the team leave the queue? It is not enough to store the index value in the queue. The difference between the current element index and the head element of the line is compared with the window size. If it is larger than the window size, the head element of the line is no longer in this window. Leave the team.

👺 code implementation

Its essence is a double-ended queue. We expand our Queue class and add pop and tail two methods, which represent removing elements from the tail of the queue and obtaining the tail value of the queue.

pop() {
  return this.items.pop();
}
tail() {
  return this.items[this.items.length - 1];
}
const maxSlidingWindow = (nums: number[], k: number) => {
  const queue = new Queue<number>();
  let maxArr: number[] = [];
  nums.forEach((item, index) => {
    while (!queue.isEmpty() && item >= nums[queue.tail()]) {
      queue.pop();
    }
    queue.enqueue(index);
    if (queue.peek() <= index - k) queue.dequeue();
    if (index >= k - 1) maxArr.push(nums[queue.peek()]);
  })
  return maxArr;
}

postscript

🔗 This article code Github link: stack , queue

🔗 Links to other articles in this series: -Sorting Algorithm


黄刀小五
305 声望22 粉丝

前端开发攻城狮, 擅长搬砖, 精通ctrl + c & ctrl + v, 日常bug缔造者.