TypeScript Algorithm Questions Actual Combat - Sword Pointing to Offer (6)

One pen, one pair of hands, one Leetcode for one night!

In this article, we will use TypeScript to solve the algorithm problem of Jianzhi offer. The problems cover a wide variety of topics, including arrays, strings, linked lists, trees, sorting and searching, and more. We'll use TypeScript's strongly typed and object-oriented features to solve these problems, and walk through practical code examples to demonstrate how to use TypeScript to solve algorithmic problems.
All the questions come from the Likou question bank: "Jianzhi Offer (2nd Edition)" The questions included in this chapter are (difficulty is my personal feeling, unofficial standard):

topic difficulty
last remaining number in circle difficulty
Maximum profit on stocks medium
Find 1+2+...+n medium
Addition without addition, subtraction, multiplication and division difficulty
build product array medium
convert string to integer medium
The nearest common ancestor of a binary search tree Simple
The nearest common ancestor of the binary tree Simple
Robot range of motion medium
H-index Simple

1. The last remaining number in the circle

1.1, topic description

0,1,···,n-1, the n numbers are arranged in a circle, starting from the number 0, each time the mth number is deleted from the circle (counting starts from the next number after deletion). Find the last number remaining in the circle.

For example, 5 numbers of 0, 1, 2, 3, and 4 form a circle, and the third number is deleted each time starting from the number 0, and the first 4 numbers to be deleted are 2, 0, 4, 1 in turn, so the last The remaining number is 3.

Example 1:

Input: n = 5, m = 3
Output: 3

Example 2:

Input: n = 10, m = 17
Output: 2

1.2. Solution

This problem is the famous "Joseph ring" problem, and the number rings are connected end to end .

First use the simulation method to think, first create a linked list with a length of n, delete the mth node in each round, until the length of the linked list is 1, and return the last remaining node. However, the simulation method needs to delete n-1 rounds, and it takes m times to search in the linked list for each round. The time complexity reaches O(n*m), and it will obviously time out.

Therefore, using dynamic programming, the most important point of the full text is to only care about the subscript change of the person who is finally alive . Let the solution be dp[i], and dp[i] represents the current subscript of the last remaining number.
For example:

    假设有一圈数字[0,1,2,3,4,5],m=3
    我们令删除后的元素的后一个元素放在最前面方便计数
    1.删除2->[3,4,5,0,1]
    2.删除5->[0,1,3,4]
    3.删除3->[4,0,1]
    4.删除1->[4,0]
    5.删除4->[0]
    尝试反推:
    如何从最后剩下的元素的索引0反推至第一轮元素索引呢?
    注意到:2->1的过程是将删除的的元素5补在尾巴上变成[0,1,3,4,5],然后再总体向右移动m位
    变成:[3,4,5,0,1];同样地,此时的最终活下来的元素0的索引由0->3
    显然这是(idx+m)%len的结果,idx为删除5后的索引,len为补上删除元素后数组的长度

Here is a picture of Mu Yi who wants to eat hot pot , which is very clear:
insert image description here

The transfer equation is: dp[i] = (dp[i - 1] + m)%i, the code is actually very simple:

function lastRemaining(n: number, m: number): number {
    
    
    let x = 0;
    for(let i = 2; i <= n; i++)
        x = (x + m) % i;
    return x;
};

Second, the maximum profit of the stock

2.1, topic description

Assuming that the price of a certain stock is stored in an array in chronological order, what is the maximum profit that can be obtained by buying and selling this stock at one time?

Example 1:
Input: [7,1,5,3,6,4]
Output: 5
Explanation: Buy on day 2 (stock price = 1), buy on day 5 (stock price = 6) Sell, maximum profit = 6-1 = 5.
Note that the profit cannot be 7-1 = 6, because the selling price needs to be greater than the buying price.

Example 2:
Input: [7,6,4,3,1]
Output: 0
Explanation: In this case, no trade is completed, so the maximum profit is 0.

2.2. Solution

1️⃣ : Double pointer arithmetic

The first pointer saves the minimum investment point encountered currently, and the second pointer saves the investment point currently encountered. Because the title requires that only one transaction can be made, the current maximum expected return can be calculated and saved while traversing.

A pointer records the minimum value visited (note that this is the minimum value visited), a pointer goes all the way back, then calculates their difference, and saves the largest one. The code is as follows:

function maxProfit(prices: number[]): number {
    
    
    if(prices.length == 0)
        return 0;
    let res = 0;
    let min = prices[0];
    for(let i = 1; i < prices.length; i++){
    
    
        min = Math.min(min, prices[i]);
        res = Math.max(prices[i] - min, res);
    }
    return res;
};

2️⃣: Monotonic stack

The principle of solving the monotonic stack is very simple. We should always keep the top element of the stack the smallest among the elements visited. If the current element is smaller than the top element of the stack, the top element of the stack will be popped out of the stack and the current element will be pushed into the stack. If the accessed element is greater than the top element of the stack, calculate the difference between him and the top element of the stack, and record the maximum value of this difference.

In fact, this method is similar to the principle of the double pointer method, except that the stack is used to save when saving.

function maxProfit(prices: number[]): number {
    
    
    if(prices.length == 0)
        return 0;
    let myStack = [];
    myStack.push(prices[0]);
    let max = 0;
    for(let i = 1; i < prices.length; i++){
    
    
        // 当前投资点小于之前的最佳投资点
        // 即栈顶元素大于prices[i],那么栈顶元素出栈,
        if(myStack[myStack.length - 1] > prices[i]){
    
    
            myStack.pop();
            myStack.push(prices[i]);
        }else{
    
    
            //栈顶元素不大于prices[i],计算prices[i]和栈顶元素的差值
            max = Math.max(max, prices[i] - myStack[myStack.length - 1]);
        }
    }
    return max;
};

3️⃣: Dynamic programming
Let the dynamic programming list dp, dp[i] represent the maximum profit of the sub-array ending with prices[i]. Since the topic is limited to "buy and sell stocks once",前i日的最大利润dp[i] 等于Max(前i-1日的最大利润,第i日卖出的最大利润)

The principle is similar to the first two methods, except that dp is used to save the previous maximum return expectation.

function maxProfit(prices: number[]): number {
    
    
    if(prices.length < 2)
        return 0;
    let min = prices[0];
    let dp:number[] = [0];
    for(let i = 1; i < prices.length; i++){
    
    
        min = Math.min(prices[i], min);
        dp[i] = Math.max(dp[i - 1], prices[i] - min);
    }
    return dp[prices.length - 1]
};

3. Find 1+2+...+n

3.1, topic description

To find 1+2+...+n, it is required not to use keywords such as multiplication and division, for, while, if, else, switch, case, and conditional judgment statements (A?B:C).

Example 1:
Input: n = 3 Output: 6

Example 2:
Input: n = 9 Output: 45

3.2. Solution

The easiest way is to use recursion to solve the problem:

function sumNums(n: number): number {
    
    
    if(n == 0)
        return 0;
    return n + sumNums(n - 1);
};

Since the topic does not allow the use of the if keyword, we use the && operator to replace it. When n is greater than 0 (n += sumNums(n - 1), it will run later to achieve the same effect as the above code.

function sumNums(n: number): number {
    
    
    return n && (n += sumNums(n - 1));
};

4. Addition without addition, subtraction, multiplication and division

4.1, topic description

“+”、“-”、“*”、“/”Write a function to calculate the sum of two integers, and it is required that the four arithmetic symbols must not be used in the function body .

Example:

Input: a = 1, b = 1 Output: 2

4.2. Solution

The title requires that operators cannot be used +,-,*,/
, so we consider using bit operations to solve the addition problem.
Let's consider the principle of addition first from the decimal system, for example, 15+17:

  1. First calculate the sum of the ones: 5 + 7 = 12, the ones is 2, ignore the carry issue
  2. Then calculate the addition of the tens place: 1 + 1 = 2, the tens place is 2
  3. Add tens digits of numbers, 22
  4. Calculate the number of carry: 5 + 7 = 12, carry 1, the number of carry is 10
  5. Adding carry digits to numbers: 10 + 22 = 32

From a binary point of view, the binary value of 15 is 01111, and the binary value of 17 is 10001

  1. First, add the numbers in each position of the computer: 01111 + 10001 = 1 1110
  2. Then calculate the number of carry, 1 + 1 = 10
  3. Then calculate the addition of 11110 and 10, and so on, until the latter number, namely b, is 0;

In the bit operation, the first step can be done with XOR, that is, ^
and the number to calculate the carry can be calculated with AND and left shift, such as 01 & 01 = 01, and then left shift to get the carry 10

function add(a: number, b: number): number {
    
    
    if(b == 0)
        return a;
    let currN = a ^ b;
    let upN = (a & b) << 1;
    return add(currN, upN);
};

5. Build a product array

5.1, topic description

Given an array A[0,1,…,n-1], construct an array B[0,1,…,n-1]where the value of B[i] is the product of the elements in array A except subscript i, ie, B[i]=A[0]×A[1]×…×A[i-1]×A[i+1]×…×A[n-1]division cannot be used.

Example:
Input: [1,2,3,4,5]
Output: [120,60,40,30,24]

5.2. Solution

The first thing that comes to mind is to divide the total product by the current number, but division cannot be used, and there are 0s in the middle of the array. If you divide by 0, there will be problems.

And if simulated with brute force:

function constructArr(a: number[]): number[] {
    
    
    let res: number[] = [];
    for(let i = 0; i < a.length; i++){
    
    
        let temp = 1;
        for(let j = 0; j < a.length; j++){
    
    
            if(j == i)
                continue;
            temp = temp * a[j];
        }
        res.push(temp);
    }
    return res;
};

Time complexity O ( N 2 ) O(N^2)O ( N2 ), will timeout:

The left and right product list method is used here, that is, construct a left list to store the product of all the numbers to the left of the index, and build a right list to store the product of all the numbers to the left of the index, for a given index i, we will use the product of all the numbers to the left of it Take the product of all the numbers to the right.

function constructArr(a: number[]): number[] {
    
    
    let left = [1];
    let right = [];
    let res = [];
    for(let i = 1; i < a.length; i++){
    
    
        left[i] = a[i - 1] * left[i - 1];
    }
    right[a.length - 1] = 1;
    for(let i = a.length - 2; i >= 0; i--){
    
    
        right[i] = a[i + 1] * right[i + 1];
    }
    for(let i = 0; i < a.length; i++){
    
    
        res[i] = left[i] * right[i];
    }
    return res;
};

Six, convert the string to an integer

6.1, topic description

Write a function StrToInt to realize the function of converting a string into an integer. Cannot use atoi or other similar library functions.

First, the function discards useless leading whitespace characters as needed until it finds the first non-whitespace character.

When the first non-null character we are looking for is a positive or negative sign, then combine the symbol with as many consecutive numbers as possible as the sign of the integer; if the first non-null character is Numbers, then directly combine them with subsequent consecutive numeric characters to form an integer.

There may be redundant characters after the valid integer part of the string, these characters can be ignored, and they should not affect the function.

Note: Your function does not need to convert if the first non-whitespace character in the string is not a valid integer character, the string is empty, or the string contains only whitespace characters.

In any case, return 0 if the function cannot perform a valid conversion.

Example 1:

Input: "42" Output: 42

Example 2:

Input: " -42" Output: -42
Explanation: The first non-blank character is '-', which is a minus sign.

Example 3:

Input: "4193 with words" Output: 4193
Explanation: Conversion ended at number '3' because its next character is not a number.

Example 4:

Input: "words and 987" Output: 0
Explanation: The first non-blank character is 'w', but it is not a number or a plus or minus sign. Therefore no valid conversion can be performed.

Example 5:

Input: "-91283472332" Output: -2147483648
Explanation: The number "-91283472332" exceeds the range of a 32-bit signed integer. Therefore returns INT_MIN (−231) .

6.2, problem solution

Here is a supplement to the match method of the RegExp object used by regular expressions: The match() method is a method of the RegExp object, which is used to retrieve strings that meet the regular expression rules in the string, and store the matched strings in the form of an array Return, the usage is as follows:

let regExp = new RegExp('/^[-+]?(\d+)/');
let res = str.match(regExp);

Here we use: regular expression processing, compare the range after extraction and then judge:

function strToInt(str: string): number {
    
    
    str = str.trim(); // 去掉前后空格
    let INT_MIN = -Math.pow(2,31), INT_MAX = Math.pow(2,31) - 1;
    let regExp = new RegExp(/^[\+\-]?\d+/);
    let res = str.match(regExp);
    console.log(res);
    if(res == null)
        return 0;
    if(res[0][0] == '-' && Number(res[0]) < INT_MIN){
    
    
        return INT_MIN;
    }
    if(Number(res[0]) > INT_MAX){
    
    
        return INT_MAX;
    }
    return Number(res[0]);
};

Figure simple method, here you can use the parseInt() function, the parseInt() function can parse a string and return an integer, the parseInt() function can handle the following situations:

  1. Parsing positive integers : When the string begins with a number, parseInt() will parse from the beginning of the string until a non-numeric character is encountered. It ignores whitespace characters at the beginning of the string and returns the parsed integer.
  2. Parsing negative integers : When a string begins with a minus sign (-), parseInt() treats it as a negative integer.
  3. Handling non-numeric characters : parseInt() will stop parsing when a string contains non-numeric characters, and return the parsed part. For example, parseInt("123abc") will return 123.
  4. Processing radix (radix): By providing the second parameter radix, the radix used for parsing can be specified. For example, parseInt("10", 2) will parse the string "10" in binary, returning 2.
  5. Ignore floating-point part : The parseInt() function ignores the decimal point and the fractional part in the string. For example, parseInt("3.14") will return 3.
  6. The parseInt() function also has some special cases, for example, when parsing a string starting with 0x, it will be treated as a hexadecimal number

Here you can call the function directly, and then judge the maximum and minimum range:

function strToInt(str: string): number {
    
    
    let m = Math.pow(2, 31);
    return Math.min(Math.max(parseInt(str) || 0, -m), m - 1);
};

Seven, the nearest common ancestor of the binary search tree

7.1, topic description

Given a binary search tree, find the nearest common ancestor of two specified nodes in the tree.

The definition of the nearest common ancestor is: "For two nodes p and q of a rooted tree T, the nearest common ancestor is expressed as a node x, satisfying that x is the ancestor of p and q and the depth of x is as large as possible (one node or its own ancestors)."

For example, given the following binary search tree: root = [6,2,8,0,4,7,9,null,null,3,5]
insert image description here

Example 1:
Input: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
Output: 6
Explanation: The nearest node 2 and node 8 The common ancestor is 6.

Example 2:
Input: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4
Output: 2
Explanation: The nearest node 2 and node 4 The common ancestor is 2, because by definition the nearest common ancestor node can be the node itself

7.2. Solution

Using the recursive method, starting from the root node, it is obvious that the value of the nearest common ancestor must be greater than one of the numbers and smaller than the other number. Using the recursive method, if the current node is greater than these two numbers at the same time, then the two must be on the left In the subtree, if the current node is smaller than these two numbers at the same time, then both of them must be in the right subtree. Search in this way until the first time one appears in the left subtree and one in the right subtree, then the condition is met:

function lowestCommonAncestor(root: TreeNode | null, p: TreeNode | null, q: TreeNode | null): TreeNode | null {
    
    
    if(root.val > p.val && root.val > q.val){
    
    
        return lowestCommonAncestor(root.left, p, q);
    }
    if(root.val < p.val && root.val < q.val){
    
    
        return lowestCommonAncestor(root.right, p, q);
    }
    return root;
};

Eight, the nearest common ancestor of the binary tree

8.1, topic description

The stem of this question is similar to the previous one, the only difference is that the binary tree here is an ordinary binary tree, not a binary search tree

8.2, problem solution

The recursive method is also used, but at this time it is necessary to judge whether there is one of p or q in the left subtree, and whether there is one of p or q in the right subtree. If there is one in the left subtree and one in the right subtree, return For the current node, if only the left subtree exists (the one found on the right is empty), then visit the left subtree; if only the right subtree exists (the one found on the left is empty), then visit the right subtree.

function lowestCommonAncestor(root: TreeNode | null, p: TreeNode | null, q: TreeNode | null): TreeNode | null {
    
    
	if(root == null)
        return null;
    if(root == p || root == q)
        return root;
    let left = lowestCommonAncestor(root.left, p, q);
    let right = lowestCommonAncestor(root.right, p, q);
    // 即在第一次该节点的左子树和右子树上分别找到了p 和 q
    if(left !== null && right !== null){
    
    
        return root;
    }
    // 暂时只找到一个
    else if(left == null){
    
    
        return right;
    }
    else if(right == null){
    
    
        return left;
    }
};

9. The range of motion of the robot

9.1, topic description

There is a square with m rows and n columns on the ground, from coordinates [0,0] to coordinates [m-1,n-1]. A robot starts to move from the grid with coordinates [0, 0], it can move one grid to the left, right, up, and down each time (it cannot move outside the grid), and it cannot enter the sum of the digits of the row coordinates and column coordinates greater than k grid. For example, when k is 18, the robot can enter square [35, 37] because 3+5+3+7=18. But it cannot enter square [35, 38] because 3+5+3+8=19. How many grids can the robot reach?

Example 1:

Input: m = 2, n = 3, k = 1
Output: 3

Example 2:

Input: m = 3, n = 1, k = 0
Output: 1

9.2, problem solution

First of all, we must understand the title. The title says that the sum of digits is not greater than k, that is, for example [13,12], the sum of digits is 1+3+1+2=7, so m = 2, n = 3, k = When 1, there are three results [0,0], [0,1], [1,0] that meet the conditions.
When m = 3, n = 1, k = 0, there is one [0,0] that meets the conditions result.

Set res as the number of global results, whether the visited array record has been visited, and then use the dfs method to traverse all cases:

function movingCount(m: number, n: number, k: number): number {
    
    
    let res = 0;
    let visited = new Array(m).fill(0).map(()=> new Array(n).fill(false));
    function dfs(i:number, j:number){
    
    
        if(sum(i, j) > k || i < 0 || j < 0 || i >= m || j >= n || visited[i][j])
            return;
        res ++;
        visited[i][j] = true; 
        dfs(i, j + 1);
        dfs(i, j - 1);
        dfs(i - 1, j);
        dfs(i + 1, j);
    }
    function sum(i:number, j:number):number{
    
    
        return Math.floor(i / 10) + i % 10 + Math.floor(j / 10) + j % 10
    }
    dfs(0, 0);
    return res;
};

10. H index

10.1, topic description

You are given an integer array citations, where citations[i] represents the number of times the researcher's ith paper has been cited. Computes and returns the researcher's h-index.

According to the definition of h-index on Wikipedia: h stands for "high citations", a scientific researcher's h-index means that he (she) has published at least h papers, and each paper has been cited at least h times. If there are several possible values ​​of h, the h index is the one with the largest value.

10.2. Solution

First sort from small to large, then traverse from back to front, set the h factor to 0, start from the article with the largest impact factor, when citations[i] > h, h++:

function hIndex(citations: number[]): number {
    
    
    citations.sort(function(a,b){
    
    return a-b});
    let h = 0;
    for(let i = citations.length - 1; i >= 0; i--){
    
    
        if(citations[i] > h){
    
    
            h++;
        }
    }
    return h;
};

Guess you like

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