TypeScript Algorithms in Practice - Stacks and Queues (implementation of stacks and queues, bracket expressions, reverse Polish expressions)

Stacks and queues are extremely important foundations of data structures. Both stacks and queues are linear lists, the same as linked list sequence lists, but stacks and queues each have their own characteristics, so they are a special kind of linear list. Queues are first-in, first-out, and stacks are first-in-last-out.

In this series of blog posts, we will learn TypeScirpt through some Likou algorithm topics. This article will focus on stacks and queues while learning TypeScipt and combating algorithms. (Some algorithm ideas refer to programmer Carl : Code Caprice )
First of all, there are no data structures such as stacks and queues in TS, and can only be replaced by arrays. When implementing stacks or queues, arrays can call the following:

  • pop(): Remove the last element from the array and return the value of the element, changing the original array.
  • push(): Adds one or more elements to the end of an array and returns the new length of the array, changing the original array.
  • shift(): Remove the first element from the array and return the value of the element, changing the original array.
  • unshift(): Adds one or more elements to the beginning of an array and returns the new length of the array, changing the original array.

Zero, TS implements stack and queue functions

0.1. Implementation stack

The core idea of ​​stack is last in first out (LIFO), then we can use array to describe stack.

What functions does a stack need to have:
push the stack, add a new element to the top of the stack (the end of the array).
Pop, remove the top element from the stack and return the removed element.
Get the top element of the stack, get the current top element of the stack and return.
Determine whether the stack is empty, and determine whether there is data in the stack (array).
Empty the stack, removing all elements from the stack.
Get the stack size and return the number of elements in the stack.
Output the data in the stack, and return all the elements in the stack as strings.

Ideas:

On the stack (push), you can use the push method of the array to add elements directly to the end of the array.
To pop the stack (pop), you can use the pop method of the array to directly remove the elements in the stack, which will return the currently removed element.
The top element of the stack (peek), the last element in the array can be obtained by the length of the array - 1.
Whether the stack is empty (isEmpty) can be realized by judging whether the length of the array is 0.
To clear the stack, you can directly assign the array to empty or call the pop method until the data in the stack is empty. Stack size (size), which can return the length of the array.
To output the data on the stack, you can call the toString method of the array to convert the array to a string.

class MyStack {
    
    
  private items: any[];
  constructor() {
    
    
    this.items = [];
  }
  // 入栈
  push(item: any) {
    
    
    this.items.push(item);
  }
  // 出栈
  pop() {
    
    
    return this.items.pop();
  }
  // 返回栈顶元素
  peek() {
    
    
    return this.items[this.items.length - 1];
  }
  // 判断栈是否为空
  isEmpty() {
    
    
    return this.items.length === 0;
  }
  // 清空栈栈内元素
  clear() {
    
    
    this.items = [];
  }
  // 获取栈内元素数量
  size(): number {
    
    
    return this.items.length;
  }
  // 将栈内元素转为字符串
  toString() {
    
    
    return this.items.toString();
  }
}

First, use the stack to implement the queue

1.1, topic description

Topic link: https://leetcode.cn/problems/implement-queue-using-stacks/
Please use only two stacks to implement a FIFO queue . The queue should support all operations supported by normal queues (push, pop, peek, empty):

Implements MyQueueclass : Push
void push(int x) element to the end of the queue Remove and return the element from the beginning of the queue Returns the element at the beginning of the queue Returns if the queue is empty ; otherwise, returnsx
int pop()
int peek()
boolean empty()true false

1.2. Examples

insert image description here

1.3. Problem solving

Use two stacks to implement the queue, one A stack is responsible for entering the queue, and one B stack is responsible for dequeuing. When you want to enter the queue, it is very simple, just push the A stack; when you want to exit the queue, if the B stack is empty, just It is necessary to pop the A stack one by one, and push the popped elements to the B stack one by one, so that the two first-in, last-out forms form a first-in, first-out queue.

class MyQueue {
    
    
    private stackIn: number[];
    private stackOut: number[];
    constructor() {
    
    
        this.stackIn = [];
        this.stackOut = [];
    }

    push(x: number): void {
    
    
        this.stackIn.push(x);
    }

    pop(): number {
    
    
        if(this.stackOut.length === 0){
    
    
            while(this.stackIn.length > 0){
    
    
                this.stackOut.push(this.stackIn.pop());
            }
        }
        return this.stackOut.pop();
    }

    peek(): number {
    
     // 返回队列开头的元素 指的是只得到开头的元素但是不弹出,这里可以先弹出再放进去
        let temp: number = this.pop();
        this.stackOut.push(temp);
        return temp;
    }

    empty(): boolean {
    
    
        return this.stackIn.length === 0 && this.stackOut.length === 0;
    }
}
/**
 * Your MyQueue object will be instantiated and called as such:
 * var obj = new MyQueue()
 * obj.push(x)
 * var param_2 = obj.pop()
 * var param_3 = obj.peek()
 * var param_4 = obj.empty()
 */

Second, use the queue to implement the stack

2.1. Topic description

Leetcode link: https://leetcode.cn/problems/implement-stack-using-queues/
Please use only two queues to implement a last-in-first-out (LIFO) stack, and support all four operations of ordinary stacks (push, top, pop and empty).

Implement the MyStack class:
void push(int x) Push element x onto the top of the stack.
int pop() removes and returns the top element on the stack.
int top() returns the top element of the stack.
boolean empty() Returns true if the stack is empty; otherwise, returns false.

Note:
You can only use the basic operations of the queue - namely push to back, peek/pop from front, size and is empty.

2.2. Examples

insert image description here

2.3. Problem solving

When using a queue, the only trouble is that when the queue is dequeued, the innermost element of the queue is taken out and then pushed into the queue until the last one is obtained, and the queue is popped.

class MyStack {
    
    
    private queue: number[];
    constructor() {
    
    
        this.queue =[];
    }

    push(x: number): void {
    
    
        this.queue.push(x);
    }

    pop(): number {
    
    
        for (let i = 0, length = this.queue.length - 1; i < length; i++) {
    
     //即把队列最里层元素拿出再压入队列,直至拿到最后一个,弹出队列
            this.queue.push(this.queue.shift()!);
        }
        return this.queue.shift()!;
    }

    top(): number {
    
    
        let res: number = this.pop();
        this.push(res);
        return res;
    }

    empty(): boolean {
    
    
        return this.queue.length === 0;
    }
}

3. Valid parentheses

3.1. Topic description

Leetcode link: https://leetcode.cn/problems/implement-stack-using-queues/
A given one only includes '(', ')', '{', '}', '[', ']' The string s, to determine whether the string is valid.

A valid string must satisfy:

Opening parentheses must be closed with closing parentheses of the same type.
Left parentheses must be closed in the correct order.
Each closing parenthesis has a corresponding opening parenthesis of the same type.

3.2. Examples

insert image description here

3.3. Problem solving

For the classic true-and-true parentheses, use the stack to judge, the left parentheses are pushed onto the stack, and the right parentheses are popped from the stack.
It should be noted that there is a possibility that the example is "(","({","({ { { { { { {}", which will lead to the correct judgment of popping the stack, but the bracket string is actually unreasonable, So in the end, it is necessary to judge whether there are still elements left on the stack, so that you can judge whether there are any left parentheses.

function isValid(s: string): boolean {
    
    
    let myStack: string[] = [];
    for(let i:number = 0; i < s.length; i++){
    
    
        let x: string = s[i];
        if(x == '(' || x == '[' || x == '{')
            myStack.push(x);
        else{
    
    
            switch(x){
    
    
                case ')':
                    if(myStack.pop() !== '(')
                        return false;
                    break;
                case ']':
                    if(myStack.pop() !== '[')
                        return false;
                    break;
                case '}':
                    if(myStack.pop() !== '{')
                        return false;
                    break;
            }
        }
    }
    return myStack.length === 0;
};

4. Evaluating Reverse Polish Expressions

4.1. Topic description

Leetcode link: https://leetcode.cn/problems/evaluate-reverse-polish-notation/

Evaluates the expression according to Reverse Polish notation.
Valid operators include +, -, *, /. Each operand can be an integer or another Reverse Polish expression.
Note that division between two integers preserves only the integer part.
It is guaranteed that the given Reverse Polish expression is always valid. In other words, the expression always yields a valid number and there is no division by zero.

4.2. Examples

insert image description here

4.3. Problem solving

Reverse Polish is also called postfix expression (write the operator after the operands).
Postfix expressions such as ab +, the operator after the two operands. Although suffix expressions look strange and are not conducive to human reading, they are good for computer processing. The advantages of converting to a postfix expression are:
1. Remove the parentheses in the original expression, because the parentheses only indicate the order of operations, not the elements that actually participate in the calculation.
2. The order of operations can be found regularly, and the computer can write codes to complete the calculation.

When the operation is performed in reverse Polish form, it is very simple to use the concept of stack to solve the problem. Numbers are pushed onto the stack, and operators are popped off the stack.

It should be noted that Math.trunc(x) is used instead of Math.floor(x) when ts is used for division. The two perform the same in the positive part, but there is a big difference in the negative number:
Math.floor(x): return less than The largest integer of a number, that is, the value of a number rounded down.
Math.trunc(x): Returns the integer part of a number, directly removing the decimal point and the parts after it.

function evalRPN(tokens: string[]): number {
    
    
    let myStack:number[] = [];
    for(let i = 0; i < tokens.length; i++){
    
    
        if(tokens[i] == '+' || tokens[i] == '-' || tokens[i] == '*' || tokens[i] == '/'){
    
    
            let second: number = myStack.pop();
            let first: number = myStack.pop();
            switch(tokens[i]){
    
    
                case '+':
                    first = first + second;
                    myStack.push(first);
                    break;
                case '-':
                    first = first - second;
                    myStack.push(first);
                    break;
                case '*':
                    first = first * second;
                    myStack.push(first);
                    break;
                case '/':
                    first = Math.trunc(first / second);
                    myStack.push(first);
                    break;
            }
        }
        else{
    
    
            myStack.push(Number(tokens[i]));
        }
    }
    return myStack.pop();
};

For the advanced method, we can use a map to design the function operations corresponding to +, -, *, /.
In the type definition of TS, => is used to represent the definition of the function, the left side is the input type, which needs to be enclosed in parentheses, and the right side of the parenthesis is the output type.

function evalRPN(tokens: string[]): number {
    
    
    const helperStack: number[] = [];
    const operatorMap: Map<string, (a: number, b: number) => number> = new Map([
        ['+', (a, b) => a + b],
        ['-', (a, b) => a - b],
        ['/', (a, b) => Math.trunc(a / b)],
        ['*', (a, b) => a * b],
    ]);
    let a: number, b: number;
    for (let t of tokens) {
    
    
        if (operatorMap.has(t)) {
    
    
            b = helperStack.pop()!;
            a = helperStack.pop()!;
            helperStack.push(operatorMap.get(t)(a, b)); //operatorMap.get(t)返回的是一个函数 函数的参数为(a,b)
        } else {
    
    
            helperStack.push(Number(t));
        }
    }
    return helperStack.pop()!;
};

Guess you like

Origin blog.csdn.net/air__Heaven/article/details/127491776