LeetCodeHOT100 hot topic 02

written in front

  • The main reason is that there are too many topics, so I will record them separately from the previous ones.
  • The pictures with many ideas are derived from Likou's solution, if there is any infringement, it will be deleted in time.
  • However, the code is implemented by individuals, so there are some understandings worth recording.
  • For the previous series of algorithms, see:

Seven, dynamic programming

1. Longest Palindromic Substring

topic description

  • Ideas :
    train of thought

  • Since it can be dp[i+1][j-1]pushed to dp [i][j], it can only traverse from bottom left to top right;

  • Since i<=j, the dp matrix is ​​an upper triangular matrix;

  • code :

class Solution {
    
    
public:
    /*
    dp[i][j]的含义为:子串[i:j]是回文子串
    dp[i][j] = true, if s[i]==s[j] && dp[i+1][j-1]==true && i+1<=j-1
    		 = true, if s[i]==s[j] && i+1==j
             = false, else
    dp[i][i] = 1
    00 01 02 03 ... 0n
       11 12 13 ... 1n
          22 23 ... 2n
          ...
                    nn
    从dp[i+1][j-1]到dp[i][j]是向右上方走,故dp的顺序是从下往上,从左边到右边
    */
    string longestPalindrome(string s) {
    
    
        int n = s.length() - 1;
        vector<vector<bool>> dp(n + 1, vector<bool>(n + 1, false));
        int re_i, re_j;  // 记录最长回文子串左右两个指针
        int re_len = 0;  // 记录最长回文子串的长度
        for(int i=n;i>=0;--i) {
    
    
            for(int j=i;j<=n;++j) {
    
    
                if(i == j) {
    
    
                    // 一个字符的子串
                    dp[i][j] = true;                    
                }
                else {
    
    
                    if(i == j - 1) {
    
    
                        // 两个字符的子串                        
                        if(s[i] == s[j]) {
    
    
                            dp[i][j] = true;
                        }
                        else {
    
    
                            dp[i][j] = false;
                        }
                    }
                    else {
    
    
                        // 大于两个字符的子串
                        if(dp[i+1][j-1] && s[i]==s[j]) {
    
    
                            dp[i][j] = true;
                        }
                        else {
    
    
                            dp[i][j] = false;
                        }
                    }
                }
                if(dp[i][j]) {
    
    
                    // [i:j]是回文子串,则尝试更新最长回文子串
                    if(j - i + 1 > re_len) {
    
    
                        re_i = i;
                        re_j = j;
                        re_len = j - i + 1;
                    }
                }
            }
        }
        return s.substr(re_i, re_len);
    }
};
Supplement: About the %d output value of bool type
  • As in the above code, the dp matrix is ​​of bool type;
  • When outputting, if the printf is as follows, it will output a non-zero integer, whether it is true or false:
printf("%d", dp[i][j]);  // 是一个非0整数
printf("%d", int(dp[i][j]));  // 是一个非0整数
  • If you want to output 0/1, you should printf as follows:
printf("%d", dp[i][j] == true);  // 若dp[i][j] = true为1
  • Only the following printf is 1, that is, 1 corresponds to true and 0 corresponds to false:
printf("%d", 1 == true); // 是1
printf("%d", 0 == false);  // 是1
  • true is 1, false is 0:
printf("%d", true); // 是1
printf("%d", false);  // 是0
  • In summary, for the sake of safety, it is better to directly use the int type instead of the bool type in both c and c++ ;
  • If you really want to use the bool type, you need to compare it with the true and false types when outputting, instead of directly outputting ;
Variant 1. Palindrome substring

topic description

  • Ideas :
  • The idea of ​​using dynamic programming is the same as that of the longest palindrome substring , but the statistics are different, here is dp[i][j]==1the number of statistics rather than j-i+1the maximum value of statistics;
  • Some dynamic programming derivations are in the comments section of the code below;
  • There is also a Manacher algorithm, the time complexity is reduced to O(N), and the space complexity is reduced to O(1), but the calculation and movement of the pointer is very troublesome, and I have not successfully reproduced >﹏<, see the blog for the principle: Manacher The algorithm is very clear;
  • code :
class Solution {
    
    
public:
    /*
    动态规划:
    dp[i][j]:s[i:j]是否是回文子串
    dp[i][j] = dp[i+1][j-1] && s[i]==s[j] if i+1<=j-1
             = s[i]==s[j]                 otherwise
    dp[i][i] = 1;
    */
    int countSubstrings(string s) {
    
    
        int n = s.length();
        vector<vector<int>> dp(n, vector<int>(n, 0));

        int re_sum = 0;
        for(int i=n-1;i>=0;--i) {
    
    
            for(int j=i;j<n;++j) {
    
    
                if(i == j) {
    
    
                    dp[i][j] = 1;
                    ++re_sum;
                }
                else {
    
    
                    if(i+1 <= j-1) {
    
    
                        if(dp[i+1][j-1] && s[i] == s[j]) {
    
    
                            dp[i][j] = 1;
                            ++re_sum;
                        }
                    }
                    else {
    
    
                        if(s[i] == s[j]) {
    
    
                            dp[i][j] = 1;
                            ++re_sum;
                        }
                    }
                }
            }
        }
        return re_sum;
    }
};

2. Longest Valid Parentheses

topic description

  • Ideas :

  • (1) The idea of ​​dynamic programming is as follows:
    train of thought

  • Perform dynamic programming for the length of the longest substring ending with the i-th character;

  • If the last two characters are (), dp[i-2]add 2 directly on the basis of , which is ()the length of ;

  • If the last two characters are )), that is, you need to see if there is one )corresponding to the last one (, that is, i-1 -dp[i-1]whether the first character is (;

    • If yes, the length is ))the length of the complete corresponding substring dp[i-1] + 2, plus ))the length of the longest previous substring dp[i-1 -dp[i-1] -1], which is equivalent to splicing two substrings;
    • If not, the substring is illegal, dp[i]=0;
  • Of course, pay attention to whether the calculated array subscript is out of bounds (less than 0) when searching forward;

  • In addition, the maximum value needs to be recorded additionally, because the meaning of the dp array is not the solution to the problem;

  • The space complexity is O(N), and the time complexity is O(N);

  • (2) You can also use the counters in left and right brackets to deal with it, which is similar to the idea of ​​double pointers:

train of thought

  • When the counts of the left and right parentheses are the same, the substring is valid;

  • When the number of right brackets is greater than the number of left brackets, the substring becomes invalid, all counts are reset to zero, and then the left pointer must point to the right pointer for synchronization;

  • However, it is impossible to count the ((()inside ()by traversing once in this way, so it is necessary to traverse again in reverse order;

  • Still when the left and right bracket counts are the same, the substring is valid;

  • When the number of left brackets is greater than the number of right brackets, the substring becomes invalid, all counts are reset to zero, and then the right pointer must point to the left pointer for synchronization;

  • Such a traversal can not be counted ())), ()but this situation has been counted in the above left-to-right traversal;

  • Note that it is more convenient if the delayed pointer initially points to the next bit , although this bit may have exceeded the range of the string;

  • Although there are statistical repetitions in the two traversals, since it is to find the maximum value, it does not matter;

  • The space complexity is O(1), and the time complexity is O(2N);

  • It is recommended to use dynamic programming to write, which is more elegant ( ̄︶ ̄)↗;

  • code :

  • (1) Dynamic programming:

class Solution {
    
    
public:
    /*
    dp[i]:以第i个字符结尾的最长子串长度
    // ()()(
    (1) s[i] = '(', 则dp[i] = 0
    // ()()
    (2) s[i] = ')' && s[i-1] = '(', dp[i] = dp[i-2] + 2
    // )((()()) 7 - 1 - 4 - 1
        s[i] = ')' && s[i-1] = ')' && dp[i-1 -dp[i-1]] = '(', dp[i] = dp[i-1 -dp[i-1] -1] + dp[i-1] + 2
    */
    int longestValidParentheses(string s) {
    
    
        vector<int> dp(s.length(), 0);
        int re_max = 0;
        for(int i=1;i<s.length();++i) {
    
    
            if(s[i] == '(') {
    
    
                dp[i] = 0;
            }
            else {
    
    
                if(s[i-1] == '(') {
    
    
                    if(i-2 >= 0) {
    
    
                        dp[i] = dp[i-2] + 2;
                    }
                    else {
    
    
                        dp[i] = 2;
                    }                    
                }
                else {
    
    
                	// 定位当前)对应的(的位置并检验是否为(
                    int leftParIndex = i - 1 - dp[i-1];
                    if(leftParIndex>=0 && s[leftParIndex] == '(') {
    
    
                        if(leftParIndex-1 >= 0) {
    
    
                            // 左括号左边还有字符串,
                            // dp[leftParIndex-1]: 0 -> )(
                            // dp[i-1]: 4 -> ()()
                            dp[i] = dp[leftParIndex-1] + dp[i-1] + 2;
                        }
                        else {
    
    
                            // 左括号左边没有字符串
                            dp[i] = dp[i-1] + 2;
                        }
                    }
                    else {
    
    
                        dp[i] = 0;
                    }
                }
            }
            // 记录最大值
            if(re_max < dp[i]) {
    
    
                re_max = dp[i];
            }
        }
        return re_max;
    }
};
  • (2) Class double pointer:
class Solution {
    
    
public:
    int longestValidParentheses(string s) {
    
    
        int re_max = 0;
        // 从左往右遍历
        int leftParNum = 0, rightParNum = 0;
        int i = s.length() - 1, j = s.length();
        while(i >= 0) {
    
    
            if(s[i] == '(') {
    
    
                ++leftParNum;
            }
            if(s[i] == ')') {
    
    
                ++rightParNum;
            }
            if(leftParNum == rightParNum) {
    
    
                int subLen = j - i;
                if(re_max < subLen) {
    
    
                    re_max = subLen;
                }
            }
            if(leftParNum > rightParNum) {
    
    
                // 则右边不可能配对了
                j = i;
                leftParNum = 0;
                rightParNum = 0;
            }
            --i;
        }
        // 从右往左遍历
        leftParNum = 0, rightParNum = 0;
        i = 0, j = -1;
        while(i < s.length()) {
    
    
            if(s[i] == '(') {
    
    
                ++leftParNum;
            }
            if(s[i] == ')') {
    
    
                ++rightParNum;
            }
            if(leftParNum == rightParNum) {
    
    
                int subLen = i - j;
                if(re_max < subLen) {
    
    
                    re_max = subLen;
                }
            }
            if(leftParNum < rightParNum) {
    
    
                // 则左边不可能配对了
                j = i;
                leftParNum = 0;
                rightParNum = 0;
            }
            ++i;
        }
        return re_max;
    }
};

[3]. Maximum subarray sum

topic description

train of thought

  • code :
class Solution {
    
    
public:
    /*
    dp[i]: 以第i个元素结尾的最大子数组和
    dp[i] = max{dp[i-1]+nums[i], nums[i]}
    另外要用一个变量存储最大值
    */
    int maxSubArray(vector<int>& nums) {
    
    
        vector<int> dp(nums.size(), 0);
        dp[0] = nums[0];
        int re_max = dp[0];
        for(int i=1;i<nums.size();++i) {
    
    
            if(dp[i-1] <= 0) {
    
    
                dp[i] = nums[i];
            }
            else {
    
    
                dp[i] = dp[i-1] + nums[i];
            }
            if(dp[i] > re_max) {
    
    
                re_max = dp[i];
            }
        }
        return re_max;
    }
};

4. Different paths [right down chessboard]

topic description

  • Ideas :

Idea 1

  • If using deep traversal search, the time complexity is O ( 2 MN ) O(2^{MN})O(2MN )O ( MN ) O(MN)of dynamic programmingO ( MN )

  • It is very similar to item 7, 6. The maximum value of the gift in Jianzhi offer algorithm question 02 , but it is a little simpler;

  • It can also be directly calculated in one step with combinatorics, as follows:
    Idea 2

  • code :

  • The dynamic programming code is as follows:

class Solution {
    
    
public:
    // dp[i][j]: 走到[i][j]有多少种可能
    // dp[i][j] = dp[i-1][j] + dp[i][j-1]
    // dp[0][j] = 1;  第一行为1
    // dp[i][0] = 1;  第一列为1
    int uniquePaths(int m, int n) {
    
    
        vector<vector<int>> dp(m, vector<int>(n, 1));
        for(int i=1;i<m;++i) {
    
    
            for(int j=1;j<n;++j) {
    
    
                dp[i][j] = dp[i-1][j] + dp[i][j-1];
            }
        }
        return dp[m-1][n-1];
    }
};

[5]. Minimum path sum [right down chessboard]

topic description

  • Ideas :
    train of thought
  • It is almost the same as the question 7, 6. The maximum value of the gift in Jianzhi offer algorithm question 02 ;
  • It is in line with the idea of ​​the above question;
  • code :
class Solution {
    
    
public:
    // dp[i][j]: 到达[i][j]时的最小路径和
    // dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j];
    int minPathSum(vector<vector<int>>& grid) {
    
    
        int m = grid.size();
        int n = grid[0].size();
        vector<vector<int>> dp(m, vector<int>(n, 0));
        for(int i=0;i<m;++i) {
    
    
            for(int j=0;j<n;++j) {
    
    
                if(i==0 && j==0) {
    
    
                    // 原点
                    dp[i][j] = grid[i][j];
                }
                if(i==0 && j!=0) {
    
    
                    // 第一行
                    dp[i][j] = dp[i][j-1] + grid[i][j];
                }
                if(i!=0 && j==0) {
    
    
                    // 第一列
                    dp[i][j] = dp[i-1][j] + grid[i][j];
                }
                if(i!=0 && j!=0) {
    
    
                    dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j];
                }
            }
        }
        return dp[m-1][n-1];
    }
};

[6]. Climbing stairs

topic description

class Solution {
    
    
public:
    // dp[i]: 到第i阶楼梯有多少种方式
    // dp[i] = dp[i-1] + dp[i-2]
    // dp[0] = 1; dp[1] = 1;
    int climbStairs(int n) {
    
    
        vector<int> dp(n+1, 0);
        dp[0] = 1;
        dp[1] = 1;
        for(int i=2;i<=n;++i) {
    
    
            dp[i] = dp[i-1] + dp[i-2];
        }
        return dp[n];
    }
};

7. Edit Distance

topic description

  • Ideas :

train of thought

  • The dp recursion formula is as follows:

d p [ i ] [ j ] = { m i n { d p [ i − 1 ] [ j ] + 1 , d p [ i ] [ j − 1 ] + 1 , d p [ i − 1 ] [ j − 1 ] } , 若 w o r d 1 [ i ] = = w o r d 2 [ j ] m i n { d p [ i − 1 ] [ j ] + 1 , d p [ i ] [ j − 1 ] + 1 , d p [ i − 1 ] [ j − 1 ] + 1 } , 若 w o r d 1 [ i ] ! = w o r d 2 [ j ] dp[i][j]=\left\{ \begin{aligned} min\{dp[i-1][j] + 1, dp[i][j-1] + 1, dp[i-1][j-1]\}, & 若 word1[i] == word2[j] \\ min\{dp[i-1][j] + 1, dp[i][j-1] + 1, dp[i-1][j-1]+1\} , &若word1[i] != word2[j] \end{aligned} \right. dp[i][j]={ min{ dp[i1][j]+1,dp[i][j1]+1,dp[i1][j1]},min{ dp[i1][j]+1,dp[i][j1]+1,dp[i1][j1]+1},young w or d 1 [ i ]==word2[j]young w or d 1 [ i ]!=word2[j]

  • dp[i][j]The meaning of:

    • The edit distance between length Yes iand word1Length Yes j;word2
  • if word1[i] == word2[j]:

    • dp[i-1][j] + 1: Add another character to the length i-1of ;word1
    • dp[i][j-1] + 1: Add another character to the length j-1of ;word2
    • Because there is only one character difference between the dp[i-1][j]sum and dp[i][j-1]and dp[i][j], it is necessary to add another character to make the two strings the same;
    • dp[i-1][j-1]dp[i-1][j-1]: The edit distance used directly ;
    • Because the last characters of the two strings are equal, there is no need to increase the edit distance if the last digit is increased;
  • if word1[i] != word2[j]:

    • Basically the same as above;
    • But dp[i-1][j-1]+1: Both strings add one more character, so the edit distance is also +1;
    • Because the last character of the two strings is not equal, it must be modified after the last character is added;
  • code :

class Solution {
    
    
public:
    // dp[i][j]: 长度i的word1和长度j的word2之间的编辑距离
    // dp[i][j] = min{dp[i-1][j] + 1, dp[i][j-1] + 1, dp[i-1][j-1]} 若word1[i] == word2[j]
    //          = min{dp[i-1][j] + 1, dp[i][j-1] + 1, dp[i-1][j-1]} 若word1[i] != word2[j]
    // dp[0][0] = 0, dp[0][j] = j, dp[i][0] = i
    int minDistance(string word1, string word2) {
    
    
        int m = word1.length();
        int n = word2.length();
        vector<vector<int>> dp(m+1, vector<int>(n+1, 0));
        for(int i=0;i<=m;++i) {
    
    
            dp[i][0] = i;
        }
        for(int j=0;j<=n;j++) {
    
    
            dp[0][j] = j;
        }
        for(int i=1;i<=m;++i) {
    
    
            for(int j=1;j<=n;++j) {
    
    
                if(word1[i-1] == word2[j-1]) {
    
    
                    dp[i][j] = min(dp[i-1][j]+1, dp[i][j-1]+1);
                    dp[i][j] = min(dp[i][j], dp[i-1][j-1]);
                }
                else {
    
    
                    dp[i][j] = min(dp[i-1][j]+1, dp[i][j-1]+1);
                    dp[i][j] = min(dp[i][j], dp[i-1][j-1]+1);
                }
            }
        }
        return dp[m][n];
    }
};

8. Different Binary Search Trees

topic description

  • Ideas :

train of thought

  • It is equivalent to asking how many shapes of the binary search tree (or binary tree) ;

  • Compared with ordinary binary trees, it is limited to binary search trees to avoid considering the arrangement of node values ​​​​after a fixed shape ;

  • The number of ordinary binary trees also needs to increase the traversal of node values ​​on the basis of binary trees , and each shape has n!a common arrangement of values;

  • code :

class Solution {
    
    
public:
    /*
    动态规划:
    dp[i]: i个节点能够组成的二叉搜索树
    dp[i]: sum(k->[0, i-1], dp[k] * dp[i-k-1])
    dp[k] => 左子树节点
    dp[i-k-1] => 右子树节点
    即dp[i] = 左右子树节点的组合数的乘积之和
    k + i-k-1 = i-1 => 除去根节点的其余节点
    dp[0] = 1;
    dp[1] = 1; 
    限制为二叉搜索树的原因是:
    1. 一旦选定某个节点为根节点,则它左边的节点和右边的节点的数量和值就都可以确定
    2. 相当于是问二叉树的形状有多少种
    3. 普通二叉树的数量还要在二叉树的基础上增加节点的遍历,每种形状共有n!种排列
    */
    int numTrees(int n) {
    
    
        vector<int> dp(n+1, 0);
        dp[0] = 1;
        dp[1] = 1;
        for(int i=2;i<=n;++i) {
    
    
            for(int k=0;k<i;++k) {
    
    
                dp[i] += dp[k] * dp[i-k-1];
            }
        }
        return dp[n];
    }
};

[9]. The best time to buy and sell stocks

topic description

  • Ideas :
  • Record the previous minimum value, and then subtract the minimum value from the current value;
  • Although it is called dynamic programming, it actually feels a bit greedy;
  • It is the same as the question 7 and 10. The maximum profit of stocks in Jianzhi offer algorithm question 02 ;
  • code :
class Solution {
    
    
public:
    /*
    dp[i] = max(prices[i]-min, max)
    */
    int maxProfit(vector<int>& prices) {
    
    
        int min_val = prices[0];
        int max_re = 0;
        int i;
        for(i=1;i<prices.size();++i) {
    
                
            if(prices[i] < min_val) {
    
    
                // 更新最低价格
                min_val = prices[i];
            }
            if(prices[i] - min_val > max_re) {
    
    
                // 更新最大收益
                max_re = prices[i] - min_val;
            }
        }
        return max_re;
    }
};

10. Word splitting

topic description

  • Ideas :
  • The state transition equation is as follows:
    dp [ i ] = dp [ i − word [ j ] . len ] & & dp [ i − word [ j ] . len : i ] = = word [ j ] dp[i] = dp[i -word[j].len] \ \&\& \ dp[i-word[j].len:i]==word[j]dp[i]=dp[iword[j].len] && dp[iword [ j ] . _ _ l e n:i]==word[j]
  • dp[i]sWhether the previous character of the meaning ican be spelled out in a dictionary;
  • code :
class Solution {
    
    
/*
    动态规划:
    1. dp[i] = dp[i-word[j].len] && dp[i-word[j].len:i]==word[j]
    2. dp[i]意为s的前i个字符是否能用字典拼出
*/
public:
    bool wordBreak(string s, vector<string>& wordDict) {
    
    
        vector<bool> dp(s.length() + 1, false);
        dp[0] = true;
        for(int i=1;i<=s.length();++i) {
    
    
            for(int j=0;j<wordDict.size();++j) {
    
    
                int len = wordDict[j].length();
                if(i-len>=0 && dp[i-len] && wordDict[j]==s.substr(i-len, len)) {
    
    
                    dp[i] = true;
                    break;
                }
            }
        }
        return dp[s.length()];
    }
};
Supplement: Two ways of comparing values ​​of the C++ string type
  1. Use str1.compare(str2) == 0, if the return value is less than 0, there is a lexicographic order str1 < str2;
  2. Use str1 == str2, if any str1 < str2, lexicographically str1 < str2;

11. Maximum Product Subarray

topic description

  • Ideas :
    train of thought
  • If it is the summation like 3. The maximum sum of consecutive sub-arrays in Jianzhi offer algorithm question 02, you only need to record the maximum sum of the string ending with the previous one to derive the maximum sum at the end;nums[i-1]nums[i]
  • But since the product has its specificity:
    • nums[i]The maximum product at the end may be obtained by multiplying two positive numbers ;
    • It may also be obtained by multiplying two negative numbers ;
    • It depends on whether the current nums[i]number is positive or negative;
  • Therefore, the maximum and minimum valuesnums[i-1] ​​that need to be recorded at the same time correspond to the above two situations respectively, so that the maximum value of the product can be obtained regardless of whether the current number is positive or negative;nums[i]
  • In fact, as long as 0the sums are multiplied, the absolute value of the product must be larger and larger;
  • In addition, you can dpuse two (or even one) temporary variables to store instead of an array nums[i-1], which is not so intuitive, but you can further optimize the space complexity to O(1) , but pay attention to the mutual calling relationship between the two;
  • code :
class Solution {
    
    
public:
    /*
    dp[i]:以nums[i]结尾的最大非空连续子数组乘积
    dp_min[i] = min{dp_min[i-1]*nums[i], dp_max[i-1]*nums[i], nums[i]}
    dp_max[i] = max{dp_min[i-1]*nums[i], dp_max[i-1]*nums[i], nums[i]}
    */
    int maxProduct(vector<int>& nums) {
    
    
        vector<int> dp_max(nums.size(), 0);
        vector<int> dp_min(nums.size(), 0);
        dp_max[0] = nums[0];
        dp_min[0] = nums[0];

        int re_max = dp_max[0];
        for(int i=1;i<nums.size();++i) {
    
    
            dp_max[i] = max(dp_max[i-1]*nums[i], nums[i]);
            dp_max[i] = max(dp_max[i], dp_min[i-1]*nums[i]);

            dp_min[i] = min(dp_max[i-1]*nums[i], nums[i]);
            dp_min[i] = min(dp_min[i], dp_min[i-1]*nums[i]);

            if(dp_max[i] > re_max) {
    
    
                // 记录最大值
                re_max = dp_max[i];
            }
        }
        return re_max;
    }
};

12. Robbery

topic description

  • Idea one :
  • dp[i]It means the maximum amount that can be obtained iby stealing the first house when walking to the first house;i
  • 则递推方程为:
    d p [ i ] = m a x ( d p [ i − 2 ] + n u m s [ i ] , d p [ i − 3 ] + n u m s [ i ] ) dp[i] = max(dp[i-2]+nums[i], dp[i-3]+nums[i]) dp[i]=max(dp[i2]+nums[i],dp[i3]+nums[i])
  • Note that the ifirst house must be stolen, so i-1the house cannot be stolen;
  • If i-2the house is stolen, then i-4if you can get a higher amount later, you can also steal it. This is because i-4and i-2does not conflict, and it is already included in i-2the sub-problem;
  • In the same way, i-3if the house is stolen, i-5the latter has also been taken into account;
  • Therefore i-2, just i-3choose a house to steal;
  • It's a bit complicated, dp[i]the meaning of the meaning is not very intuitive, and the reasoning may not be correct, but this is a recursive relationship that I came up with ( •̀ .̫ •́ )✧;
  • Code one :
class Solution {
    
    
public:
    /*
    dp[i]:走到i房屋时偷窃i房屋可得的最高金额
    dp[i] = max(dp[i-2]+nums[i], dp[i-3]+nums[i])
    */
    int rob(vector<int>& nums) {
    
    
        vector<int> dp(nums.size(), 0); 
        int re_max = 0;   

        for(int i=0;i<nums.size();++i) {
    
    
            if(i <= 1) {
    
    
                dp[i] = nums[i];
            }
            else {
    
    
                dp[i] = max(dp[i], dp[i-2]+nums[i]);
                if(i >= 3) {
    
    
                    dp[i] = max(dp[i], dp[i-3]+nums[i]);
                }
            }                        
            re_max = max(re_max, dp[i]);
        }
        return re_max;
    }
};
  • Idea 2 :
    Idea two

  • It is another way of thinking, which means that the highest amount can be obtained when dp[i]passing through the first house, and the first house can not be stolen;ii

  • Then either steal the first ihouse, and the amount is i-2the maximum available when the first house is added nums[i];

  • Either don’t steal, the i-1maximum amount can be obtained when the first house is used;

  • Code two :

class Solution {
    
    
public:
    /*
    dp[i]:走到i房屋时可得的最高金额
    dp[i] = max(dp[i-2]+nums[i], dp[i-1])
    */
    int rob(vector<int>& nums) {
    
            
        if(nums.size() == 1) {
    
    
            return nums[0];
        }
        if(nums.size() == 2) {
    
    
            return max(nums[0], nums[1]);
        }
        vector<int> dp(nums.size(), 0); 
        dp[0] = nums[0];
        dp[1] = max(dp[0], nums[1]);

        for(int i=2;i<nums.size();++i) {
    
    
            dp[i] = max(dp[i-1], dp[i-2]+nums[i]);    
        }
        return dp[nums.size()-1];
    }
};
Variation 1. Robbery III

topic description

  • Ideas :
  • Although it is a variant, the idea of ​​processing is quite different. Although some clues of dynamic programming can still be seen, the overall idea has become the post-order traversal of the tree ;
  • Each node considers the maximum amount that its subtree can obtain after robbing and not robbing ;

train of thought

  • the code
class Solution {
    
    
private:
    struct returnType {
    
    
        int rob;  // 打劫该节点时子树可获得的最大值
        int unrob;  // 不打劫该节点时子树可获得的最大值
    };

    returnType dfs(TreeNode *root) {
    
    
        if(root == nullptr) {
    
    
            return {
    
    0, 0};
        }
        returnType left = dfs(root->left);
        returnType right = dfs(root->right);
        // 打劫root,则子节点都不能打劫
        int root_rob = root->val + left.unrob + right.unrob;
        // 不打劫root,则子节点可以打劫也可以不打劫
        int root_unrob = max(left.rob, left.unrob) + max(right.rob, right.unrob);
        return {
    
    root_rob, root_unrob};
    }
public:
    int rob(TreeNode* root) {
    
    
        returnType re = dfs(root);
        return max(re.rob, re.unrob);
    }
};

13. Largest square

topic description

  • Ideas :

  • The transfer equation is as follows:
    transfer equation

  • An example is as follows:
    train of thought

  • Explanation of the state transition equation:
    Description of transfer equation

  • In addition, the first column and the first row need to be initialized, '1'and dp[i][j] = 1the element values ​​on the left, upper and upper left corners do not need to be considered;

  • code :

class Solution {
    
    
public:
    /*
    dp[i][j]:以matrix[i][j]结尾的正方形的最大边长
    dp[i][j] = min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]) + 1 (if matrix[i][j]==1)
               0                                             (otherwise)
    */
    int maximalSquare(vector<vector<char>>& matrix) {
    
    
        vector<vector<int>> dp(matrix.size(), vector<int>(matrix[0].size(), 0));
        int re_max = 0;

        // 初始化第一列和第一行
        for(int i=0;i<matrix.size();++i) {
    
    
            if(matrix[i][0] == '1') {
    
    
                dp[i][0] = 1;
            }
            if(dp[i][0] > re_max) {
    
    
                re_max = dp[i][0];
            }
        }
        for(int j=0;j<matrix[0].size();++j) {
    
    
            if(matrix[0][j] == '1') {
    
    
                dp[0][j] = 1;
            }
            if(dp[0][j] > re_max) {
    
    
                re_max = dp[0][j];
            }
        }
        
        // 计算剩下的dp
        for(int i=1;i<matrix.size();++i) {
    
    
            for(int j=1;j<matrix[0].size();++j) {
    
    
                if(matrix[i][j] == '1') {
    
    
                    dp[i][j] = min(dp[i-1][j-1], min(dp[i-1][j], dp[i][j-1])) + 1;
                }
                else {
    
    
                    dp[i][j] = 0;
                }
                if(dp[i][j] > re_max) {
    
    
                    re_max = dp[i][j];
                }
            }
        }
        // 返回面积 = 边长*边长
        return re_max*re_max;
    }
};

14. Perfect Square Number [Complete Backpack]

topic description

  • Ideas :
  • It is a complete knapsack problem , that is, neach complete square number can be used multiple times ;
  • The points to note are as follows:
    • It is exactly composed , so only or can have an initial value nwhen initializing , and the rest is (because it is a minimization problem);dp[0][0]dp[0]INT_MAX
    • In one-dimensional form, dpthe array space is n+1two loops , and the inner loop uses positive order traversal;
    • Outer loop traversal value, here is the type of complete square number, sqrt(n)just traverse to it;
    • The inner loop traverses weightto n, weighthere is the space occupied by the perfect square number, namely value*value;
  • In fact, it is similar to the change problem. The change problem is also a complete knapsack problem, which is also exactly full n;
  • code :
class Solution {
    
    
public:
    /*
    dp[i][j]:用前i个完全平方数,和为j的最小数量
    dp[i][j] = min(dp[i-1][j], dp[i-1][j-i^2]+1);
    降为一维,dp[j] = min(dp[j], dp[j-i^2]+1);
    初始值:dp[0] = 0,其余为INT_MAX
    */
    int numSquares(int n) {
    
    
        int max_value = int(sqrt(n));
        vector<int> dp(n+1, INT_MAX);
        dp[0] = 0;
        for(int i=1;i<=max_value;++i) {
    
    
            int weight = i*i;
            for(int j=weight;j<=n;++j) {
    
    
                dp[j] = min(dp[j], dp[j-weight]+1);
            }
        }
        return dp[n];
    }
};

15. Longest increasing subsequence

topic description

  • Ideas :
    train of thought
  • The idea of ​​dynamic programming is relatively easy to think of, but the time complexity is O(N^2);
  • In addition, there is a more complicated idea, the time complexity can be reduced to O(NlogN), but I don’t want to study it for the time being ( tired, akimbo );
  • I feel that the idea of ​​dynamic programming is relatively orthodox, and it is the first medium dynamic programming question written by myself;
  • code :
class Solution {
    
    
public:
    /*
    dp[i]:以nums[i]结尾的最长严格递增子序列长度
    dp[i] = j:0->i-1 max(dp[j]+1) && nums[j]<nums[i]
    => dp[i]初始值为1
    */
    int lengthOfLIS(vector<int>& nums) {
    
    
        vector<int> dp(nums.size(), 1);
        int re_max = 1;
        for(int i=1;i<nums.size();++i) {
    
    
            for(int j=0;j<i;++j) {
    
    
                if(nums[j] < nums[i]) {
    
    
                    dp[i] = max(dp[i], dp[j]+1);
                    // 记录全局最大值
                    re_max = max(re_max, dp[i]);
                }
            }
        }
        return re_max;
    }
};

16. The best time to buy and sell stocks includes a freezing period

topic description

- Ideas :
train of thought
train of thought

  • Points to note are as follows:
    • The dp matrix records the state at the end of each day ;
    • In other words, the stocks sold yesterday have passed the freezing period at this time;
    • Only the stocks sold on the same day are within the cooling-off period;
    • The distinction between states would be blurred if not designed this way;
  • code :
class Solution {
    
    
public:
    /*
    动态规划,每一天结束时有三种状态:
    dp[i][0]:持有股票
    dp[i][1]:未持有股票且在冷冻期
    dp[i][2]:未持有股票且不在冷冻期
    */
    int maxProfit(vector<int>& prices) {
    
    
        int n = prices.size();
        vector<vector<int>> dp(n, vector<int>(3, 0));
        dp[0][0] = -prices[0];  // 买入当天股票
        dp[0][1] = 0;
        dp[0][2] = 0;
        for(int i=1;i<n;++i) {
    
    
            // 1. 继续持有或者当天买入
            dp[i][0] = max(dp[i-1][0], dp[i-1][2] - prices[i]);
            // 2. 今天卖出
            dp[i][1] = dp[i-1][0] + prices[i];
            // 3. 昨天卖出或者昨天就已经不在冷冻期
            dp[i][2] = max(dp[i-1][1], dp[i-1][2]);
        }
        // 最后一天的2和3的最大值一定大于1
        int re_max = max(dp[n-1][1], dp[n-1][2]);
        return re_max;
    }
};

17. Change Exchange [Complete Backpack]

topic description

  • Ideas :
  • It is the idea of ​​the complete knapsack problem, which can be reduced to one-dimensional dynamic programming;
  • Note that it is just full of problems, only during initialization dp[0] = 0, and the rest is the maximum value;
  • In the final comparison, because the title states that all coins are integers, the smallest is 1, and the largest solution is also amounta coin, so you can know whether the current value comes from by comparing amountwith and (that is, it cannot be exactly filled), of course. can be used for comparison;dp[amount]INT_MAXINT_MAX
  • code :
class Solution {
    
    
public:
    /*
    dp[i][j]:用前i个硬币凑j面值的最少硬币数
    dp[0][0] = 0;    dp[0][j] = INT_MAX;
    降为一维:
    dp[0] = 0;  
    dp[j] = min(dp[j], dp[j-weight[i]]+1);
    */
    int coinChange(vector<int>& coins, int amount) {
    
    
        vector<int> dp(amount+1, INT_MAX);
        dp[0] = 0;
        for(int i=0;i<coins.size();++i) {
    
    
            for(int j=coins[i];j<=amount;++j) {
    
    
                if(dp[j-coins[i]] != INT_MAX) {
    
    
                	// 不是从INT_MAX而来,以防溢出
                    dp[j] = min(dp[j], dp[j-coins[i]] + 1);
                }                
            }
        }
        if(dp[amount] == INT_MAX) {
    
    
            return -1;
        }
        else {
    
    
            return dp[amount];
        }
    }
};
Variant 1. Change II

topic description

  • Ideas :

  • It is very similar to 17. Exchange of change , and it happens to be full of questions;

  • But the initial values ​​are different:

    • If 0 coins are used to make up 0 yuan, there is a combination, that is dp[0][0]=1;
    • For the situation that cannot be combined, it can be directly recorded as no combination number, that is dp[0][j]=0;
  • The state transition formula is also different, which is the addition of the two methods, instead of taking their maximum value + 1;

  • Notice:

    • This is because the method of taking coins is the number of combinations, and the sequence is not considered when taking each time , so it can be done with dynamic programming;
    • If the order of taking is considered, such as (1,1,2), (1,2,1) and (2,1,1) are three different methods, this method cannot be used (at least this template cannot be used) bar>︿<);
  • code :

class Solution {
    
    
    /*
    dp[i][j]:用前i个硬币恰好凑j面值的组合数
    dp[0][0] = 1;    dp[0][j] = 0;
    降为一维:
    dp[0] = 1;  
    dp[j] = dp[j] + dp[j-weight[i]];
    */
public:
    int change(int amount, vector<int>& coins) {
    
    
        vector<int> dp(amount+1, 0);
        dp[0] = 1;
        for(int i=0;i<coins.size();++i) {
    
    
            for(int j=coins[i];j<=amount;++j) {
    
    
                dp[j] = dp[j] + dp[j-coins[i]];
            }
        }
        return dp[amount];
    }
};

18. Bit counting

topic description

  • Ideas :
  • In fact, it is to find the law;
  • The inumber of 1 in the number is actually i-stepthe number of i in the number plus 1;
  • stepis ithe largest power of two that does not exceed;
  • That is to say, dp[i]it must dp+1come from one of the previous ones, so dynamic programming can be used;
  • A little bit of the meaning of something that has already existed (ah, good literature and art);
  • code :
class Solution {
    
    
public:
    /*
    000:0
    001:1 = [000] + 1  dp[1] = dp[0] + 1 = dp[i-1] + 1
    010:1 = [000] + 1  dp[2] = dp[0] + 1 = dp[i-2] + 1 
    011:2 = [001] + 1  dp[3] = dp[1] + 1 = dp[i-2] + 1
    100:1 = [000] + 1  dp[4] = dp[0] + 1 = dp[i-4] + 1
    101:2 = [001] + 1  dp[5] = dp[1] + 1 = dp[i-4] + 1
    110:2 = [010] + 1
    111:3 = [011] + 1
    */
    vector<int> countBits(int n) {
    
    
        vector<int> dp(n+1, 0);

        int step = 1;
        for(int i=1;i<=n;++i) {
    
    
            if(i >= 2*step) {
    
    
                step *= 2;
            }
            dp[i] = dp[i-step] + 1;
        }

        return dp;
    }
};

19. Split Equal Sum Subsets [0/1 Knapsack]

topic description

  • Ideas :
  • The original question is equivalent to: take a number of numbers from the array, can it just fill the backpack with the capacity of the array and half ;
  • Find the array sum first, and then use the 0/1 knapsack solution;
  • Another point to note is as follows:
    • is exactly full of questions;
    • Only one element or the sum of the array is odd does not meet the requirements;
  • code :
class Solution {
    
    
public:
    /*
    动态规划:0/1背包问题
    => 原问题可以转换为求是否能恰好装满数组和一半的背包
    dp[i][j]:前i个元素能否恰好装满j
        由于不需要value,即不用求最少/最多的元素数量
        所以dp数组的类型可以是bool类型
    转移方程: 
        dp[i][j] = dp[i-1][j] || dp[i-1][j-nums[i]]
    转为一维: 
        dp[j] = dp[j] || dp[j-nums[i]]
    */
    bool canPartition(vector<int>& nums) {
    
    
        if(nums.size() <= 1) {
    
    
            // 仅一个元素不符合要求
            return false;
        }

        // 求数组和
        int num_sum = 0;
        for(int i=0;i<nums.size();++i) {
    
    
            num_sum += nums[i];
        }

        if(num_sum % 2 == 1) {
    
    
            // 奇数和也不符合要求
            return false;
        }

        // dp
        int target = num_sum / 2;  // 背包容量
        vector<bool> dp(target+1, false);
        dp[0] = true;        
        for(int i=0;i<nums.size();++i) {
    
    
            for(int j=target;j>=0;--j) {
    
    
                if(j-nums[i] >= 0) {
    
    
                    dp[j] = dp[j] || dp[j-nums[i]];
                }                
            }
        }
        return dp[target];
    }
};

20. Target and [0/1 Backpack]

topic description

  • Ideas :
  • In fact, it can be converted into a 0/1 knapsack problem, and then use dynamic programming to do it;

train of thought

  • Why not use positive as the capacity of the backpack?
  • Because the derivation of positive is that (target + sum) / 2it may be negative, so it cannot be used as the capacity of the backpack, and negative can prove that it must be non-negative, because it is targetalways less than or equal to sum;
  • However, if targetit is illegal, that is to say, there is no way to add up all the numbers in the array to make one target, then negativeit may be a negative number, so it is necessary to judge in advance to rule out this situation;
  • Under what circumstances can it be turned into a 0/1 knapsack problem?
  • In fact, this question is very similar to 19. Segmentation and subsets . It also divides the problem into two, and then only considers whether the situation on one side is satisfied. If it is satisfied, the situation on the other side can also be satisfied;
  • Both are a definite number that can be deduced from the conditions. This number must meet the non-negative requirements, and both are related to the sum of arrays ;
  • It can only be said that it is still very clever;
  • code :
class Solution {
    
    
public:
    /*
    假设所有数之和是sum,添+的数之和是positive,添-的数之和是negative,则有
    sum = positive + negative
    => target = positive - negative = sum - 2*negative = 2*positive - sum
    由于target和sum已知,因此positive和negative均可以算出来
    => negative = (sum - target) / 2
    => positive = (target + sum) / 2
    => 也就是原问题等价于能不能用nums中的元素恰好凑出positive或者negative
    => 但由于target <= sum且target可以为负数,因此negative一定非负,但positive有可能是负数
    => 所以只能用negative作为背包的容量
    因此转换成一个0/1背包问题
    dp[i][j]:用前i个数恰好能凑出j的组合数
    转移方程:
        dp[i][j] = dp[i-1][j] + dp[i-1][j-nums[i]]
    初始化:
        dp[0][0] = 1
    */
    int findTargetSumWays(vector<int>& nums, int target) {
    
    
        int num_sum = 0;
        for(int i=0;i<nums.size();++i) {
    
    
            num_sum += nums[i];
        }

        if(num_sum < target) {
    
    
            // sum一定会大于等于target,否则非法
            return 0;
        }

        if((num_sum - target) % 2 == 1) {
    
    
            // positive不是整数,则target非法
            return 0;
        }
        
        int negative = (num_sum - target) / 2;
        vector<int> dp(negative+1, 0);
        dp[0] = 1;
        for(int i=0;i<nums.size();++i) {
    
    
            for(int j=negative;j>=nums[i];--j) {
    
    
                dp[j] = dp[j] + dp[j - nums[i]];
            }
        }
        return dp[negative];
    }
};

21. Poke the Balloon

topic description

  • Ideas :
  • It's hard to think of dynamic programming ::>_<::, and I don't understand the official thinking, mainly because the design of the boundary and dynamic programming is very ingenious, and after reading a big guy's solution, I suddenly became enlightened and laughed What a irascible little brother , as follows:

train of thought

train of thought

  • Some incomprehensible points:

  • (1) Why not calculate the dp value in the case of i==j(one balloon left) and (two balloons left)?i+1==j

  • Because according to the definition, dp[i][j]it does not contain isums j, that is to say, at least three balloons are required to calculate;

  • In addition, from the perspective of the entire algorithm, although the remaining one balloon and the remaining two balloons appear in the sub-problem, it is not realistic, because the remaining one balloon and the remaining two balloons can always return to a larger Make enough three balloons in the sub-problem to pop them, instead of popping them when there are only one balloon left and two balloons left;

  • Of course, if there are no three balloons in total , it is necessary to discuss the situation of popping one balloon and popping two balloons;

  • (2) Why add two pseudo-balloons, one in front and one in the back?

  • On the one hand, it is to avoid the discussion that the sum of all the balloons is not enough for three , adding two pseudo-balloons will certainly be enough for three;

  • On the other hand, if the number of balloons exceeds two, we have no way of knowing which two balloons are left for us to pop . At this time, we need to loop through all the possibilities for discussion. To avoid this trouble, we can add The two pseudo-balloons integrate this discussion into sub-problems , because sub-problems are also kdiscussed through combination traversal;

  • (3) How is the filling order of the dp array determined?

  • A convenient method is to look at the position of the last returned dp subscript matrix, here is the return dp[0][n+1], in the upper right corner of the matrix , so it traverses from bottom to top, from left to right ;

  • code :

class Solution {
    
    
public:
    /*
    动态规划:
    dp[i][j]:戳破(i:j)之间的所有气球可以获得的硬币最大数量,注意是开区间
    1. 转移方程:
    dp[i][j] = {k:i+1->j-1}max(dp[i][k] + nums[i]*nums[k]*nums[j] + dp[k][j]) if i+1<=j-1
             = nums[i]*nums[j] + max(nums[i], nums[j])                        if i+1==j
             = nums[i]                                                        if i==j
    2. 剩两个气球和剩一个气球的情况无需考虑
        因为如果nums.size()>=3,则在戳气球的实际过程中不可能会有两个和一个气球的情况
        虽然子问题里面会有,但并不处理(戳破),而是直接返回到更大的问题(>=3)中再来戳
    3. 由于最后剩下的两个气球可以是数组中的任意两个气球
        所以一定要增加一前一后两个伪气球
        增加了两个气球之后,也不用讨论剩下的气球数目,因为一定是>=3的
    */ 
    int maxCoins(vector<int>& nums) {
    
    
        int n = nums.size();

        // 增加一前一后两个伪气球,值为1
        vector<int> new_nums(n+2, 1);
        for(int i=0;i<n;++i) {
    
    
            new_nums[i+1] = nums[i];
        }

        vector<vector<int>> dp(n+2, vector<int>(n+2, 0));
        for(int i=n+2-1;i>=0;--i) {
    
    
            for(int j=i;j<n+2;++j) {
    
    
                if(i == j) {
    
    
                    // 剩一个气球,不讨论
                    //dp[i][j] = new_nums[i];
                }
                if(i+1 == j) {
    
    
                    // 剩两个气球,不讨论
                    //dp[i][j] = new_nums[i] * new_nums[j] + max(new_nums[i], new_nums[j]);
                }
                if(i+1 <= j-1) {
    
    
                	// 有三个气球
                    for(int k=i+1;k<=j-1;++k) {
    
    
                        dp[i][j] = max(dp[i][j], dp[i][k] + new_nums[i]*new_nums[k]*new_nums[j] + dp[k][j]);
                    }
                }
            }
        }
        return dp[0][n+1];
    }
};

Eight, dichotomy

1. Find the median of two ordinal arrays

topic description

  • Ideas :
  • Very clever dichotomy (so difficult 〒▽〒);
  • It is probably necessary to use the condition of order to find the kth number :
    • Use iand jtwo pointers to calibrate the numbers that have been excluded from the current two arrays;
    • Compare the two numbers of the sum i+k/2and respectively j+k/2, and discard k/2the number of the smaller one;
    • This is because these numbers must be on the left side of the median, k/2just to divide the k numbers into two arrays;
    • Then delete kfrom it k/2and continue comparing;
  • Three situations need to be paid attention to when comparing :
    1. If the two numbers in the sum of the two arrays are not out of bounds, it can be handled as i+k/2above ;j+k/2
    2. If one of the two arrays is out of bounds, you cannot discard k/2the number, but try to discard min(a.size()-i,b.size()-j)the number;
    3. If one array pointer has come to an end, just discard kthe number in another array (without having to divide by two k/2);
  • The number moved by the last moving pointer is the kth number (it may be a ipointer or ja pointer, so a variable needs to be used to record each movement);
  • Why is it looking for the kth number instead of directly looking for the median?
  • Because the median may be the kth number (odd number), or it may be the average of the kth number and the k+1th number (even number);
  • A binary traversal cannot find the two adjacent numbers of the kth number and the k+1th number at the same time, only the kth number;
  • Because under the condition of dichotomy, there is no condition for judging numbers to be adjacent;
  • Therefore, only a function to find the kth number can be encapsulated as an aid to find the median;
  • Some graphic explanations are as follows:

train of thought
train of thought

  • code :
class Solution {
    
    
private: 
    /*
    findKthSortedArrays:返回两个数组中第rest个数的数值
    */
    int findKthSortedArrays(vector<int>& nums1, vector<int>& nums2, int rest) {
    
    
        int i = -1, j = -1;
        double re;
        int move;
        while(rest > 0) {
    
    
            if(rest == 1) {
    
    
                move = 1;
            }
            else {
    
    
                move = rest / 2;  // 取一半步长则较小的一方必定都是在中位数左边的
            }
            if(i+move < nums1.size() && j+move <nums2.size()) {
    
    
                // 按照rest的一半前进
                if(nums1[i+move] > nums2[j+move]) {
    
    
                    // nums2可以前进                    
                    j += move;
                    re = nums2[j];
                }
                else {
    
    
                    // nums1可以前进
                    i += move;
                    re = nums1[i];
                }
                rest -= move;
            }
            else {
    
    
                move = min(nums1.size()-i-1, nums2.size()-j-1);
                if(move == 0) {
    
    
                    // 直接取另一个数组的第rest个数
                    if(i == nums1.size() - 1) {
    
    
                        // nums2可以前进    
                        j += rest;
                        re = nums2[j];
                    }
                    else {
    
    
                        // nums1可以前进
                        i += rest;
                        re = nums1[i];
                    }
                    rest = 0;
                }
                else {
    
    
                    // 按照最短的move前进
                    if(nums1[i+move] > nums2[j+move]) {
    
    
                        // nums2可以前进                    
                        j += move;
                        re = nums2[j];
                    }
                    else {
    
    
                        // nums1可以前进
                        i += move;
                        re = nums1[i];
                    }     
                    rest -= move;    
                }   
            }
        }
        return re;
    }
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
    
    
        int len = nums1.size() + nums2.size();
        int rest = len / 2;
        if(len % 2 == 0) {
    
    
            // 中位数有两个,取平均
            return 1.0 * (findKthSortedArrays(nums1, nums2, rest) + findKthSortedArrays(nums1, nums2, rest+1)) / 2;
        }
        else {
    
    
            // 中位数有一个,直接返回
            return 1.0 * findKthSortedArrays(nums1, nums2, rest+1);
        }
    }
};

2. Search a rotated sorted array

topic description

  • Ideas :
  • In fact, the ordered side is used to targetjudge the position, so it can be processed by dichotomy like the ordered array;
  • The core points are as follows:
    • It is possible to have order on one side and disorder on the other, or order on both sides;
    • The ordered side must be in ascending order;
    • Judging whether it is on the ordered side requires judging the head and tail of the ordered sequence at the same time. This is different from the dichotomy of ordinary ordered arrays, because simply comparing the minimum value of the ordered sequence cannot determine that it must be targetin targetthe ordered sequence. Because there may be larger values ​​on the unordered side;
  • In addition, the judgment of the boundary is actually quite complicated. It is best to list all possible situations as an example first , and then consider how to determine the boundary to ensure that no situation is missed;

train of thought

  • When implementing the code, because the dichotomy method is used, it is necessary to use it low = mid + 1. Although this inscription low = midwill not be endlessly looped, it +1is better to write it just to be on the safe side;

  • code :

class Solution {
    
    
public:
    /*
    主要是利用了有序的一侧来进行判断,所以可以和有序数组一样用二分法处理
    1. 有可能一侧有序一侧无序,也有可能两侧均有序
    2. 有序的一侧一定是升序
    mid的可能情况如下:
    (1) 4,5,6,7(mid),0,1,2
    (2) 4,5,6(mid),7,0,1,2
    (3) 4,5,6,7,0(mid),1,2
    (4) 7(mid),0
    */
    
    int search(vector<int>& nums, int target) {
    
    
        int low = 0, high = nums.size() - 1;
        int re;
        while(low < high) {
    
    
            int mid = low + (high - low) / 2;
            if(nums[mid] >= nums[low]) {
    
    
                // 有序在左侧[low, mid]
                if(nums[mid] >= target && nums[low]<=target) {
    
    
                    // [low, target, mid]
                    high = mid;
                }
                else {
    
    
                    low = mid + 1;
                }
            }
            else {
    
    
                // 有序在右侧[mid, high]
                if(nums[mid] < target && nums[high] >= target) {
    
    
                    // (mid, target, high]
                    low = mid + 1;
                }
                else {
    
    
                    high = mid;
                }
            }
        }
        if(nums[low] == target) {
    
    
            return low;
        }
        else {
    
    
            return -1;
        }
    }
};

[3]. Find the first and last position of an element in a sorted array

topic description

  • Ideas :
  • Two binary search:
    • Find the first targetsubscript equal to;
    • Find the first targetsubscript greater than;
  • Note the boundary conditions:
    • There may be no targetsubscript equal to;
    • There may be no targetsubscript greater than;
  • It is almost the same question as 3. Find the number I in the sorted array in Question 02 of Jianzhi offer algorithm , and the core idea is the same;
  • In addition, when using the dichotomy, pay attention to the value of moving lowand hightime mid, and do not use the value of lowor by mistake high;
  • code :
class Solution {
    
    
private:
    // 搜紧确下界,第一个为target的值
    int searchLowerBound(vector<int>& nums, int target) {
    
    
        int low = 0, high = nums.size() - 1;
        while(low < high) {
    
    
            int mid = low + (high - low) / 2;
            if(nums[mid] < target) {
    
    
                low = mid + 1;
            }
            else {
    
    
                high = mid;
            }
        }
        if(nums[low] == target) {
    
    
            return low;
        }
        else {
    
    
            // 不存在第一个为target的值
            return -1;
        }
    }
    // 搜上界,第一个大于target的值
    int searchUpperBound(vector<int>& nums, int target) {
    
    
        int low = 0, high = nums.size() - 1;
        while(low < high) {
    
    
            int mid  = low + (high - low) / 2;
            if(nums[mid] <= target) {
    
    
                low = mid + 1;
            }
            else {
    
    
                high = mid;
            }
        }
        if(nums[low] > target) {
    
    
            return low;
        }
        else {
    
    
            // 不存在第一个大于target的值
            return -1;
        }
    }
public:
    vector<int> searchRange(vector<int>& nums, int target) {
    
    
        if(nums.empty()) {
    
    
            return {
    
    -1, -1};
        }
        int low = searchLowerBound(nums, target);
        int high = searchUpperBound(nums, target);
        if(low == -1) {
    
    
            return {
    
    -1, -1};
        }
        else {
    
    
            if(high == -1) {
    
    
                return {
    
    low, int(nums.size())-1};
            }
            else {
    
    
                return {
    
    low, high - 1};
            }
        }
    }
};

4. Find duplicates

topic description

  • Ideas :

  • In fact, it is quite a difficult question to understand;

  • The core points of comparison are as follows:

    1. The domain of the subscript is [0, n], the value range is [1, n], basically the same domain ;
    2. Only one number repeats, and it can repeat multiple times;
  • Idea 1: binary search
    binary search

  • The time complexity is O(NlogN), and the space complexity is O(1);

  • The more critical points are:

    • Statistics cntare the quantity less than or equal tonums[i] , must include equal to;
    • The goal is to find the minimumcnt > nums[i] that satisfies ;nums[i]
    • Binary search requires the original array to be ordered. Although the array values ​​are unordered, the array values ​​and array indexes are basically in the same domain and ordered , so the array index can be used instead of the array value for binary search;
  • See the code section for some derivations;

  • Code one :

class Solution {
    
    
public:
    /*
    [1, n]中只有一个重复的数,共有n+1个数
    1 3 4 2 2
    nums[i]: 1 2 3 4
    cnt:     1 3 4 5
    假设当前数是nums[i],则统计小于等于nums[i]的数
    1. 若less_cnt <= nums[i],则nums[i]不是重复的数,而且小于重复的数
       ==:意味着小于nums[i]的数没有缺失
       <: 意味着小于nums[i]的数有缺失,但一定没有重复,因为只能有一个数重复
    2. 若less_cnt > nums[i],则最小的nums[i]是重复的数,其余的是大于重复的数
    因为前提是:
    1. 仅有一个数重复;
    2. 共有n个数;
    3. 数均在[1, n]中;
    于是问题转化为找第一个满足less_cnt > nums[i]的nums[i]
    本来是需要先排序在做二分查找的,但这里限制了不能修改数组,又有额外条件:
    1. 数组下标是[0, n],而且数组下标是有序的
    所以可以用数组下标的[1, n]代替nums[i]作为二分查找的左右端
    */
    int findDuplicate(vector<int>& nums) {
    
    
        int low = 1, high = nums.size()-1;
        while(low < high) {
    
    
            int mid = low + (high-low)/2;
            int less_cnt = 0;
            for(int i=0;i<nums.size();++i) {
    
    
                if(nums[i] <= mid) {
    
    
                    ++less_cnt;
                }
            }
            if(less_cnt <= mid) {
    
    
                low = mid + 1;
            }
            else {
    
    
                high = mid;
            }
        }
        return low;
    }
};
  • Idea 2: Double pointer
  • In fact, the time complexity of double pointer can reach O(N), which is the optimal solution ;
  • But here we still put this question in the dichotomy, because this dichotomy is very clever and typical, and the idea of ​​double pointers is hard to think of. The difficulty is how to convert this question into a directed graph to find the first entry The problem of the link point ;
    Idea two
  • The more critical points are:
    • 0Can be seen as a pseudo-head node;
    • The derivation is to assume that all values ​​​​are not repeated , and then determine the shape of the graph from the in-degree and out-degree;
    • Then increase the repeated value one by one, considering the possibility of the shape of the graph at this time;
  • After the conversion, it is the same topic as 7. Variant 1. Ring Linked List II of 10. Double Pointer ;
  • See the code section for some derivations;
  • Code two :
class Solution {
    
    
public:
    /*
    有n+1个数,取值[1, n],下标[0, n]
    推导如下:
    1. 假设这n+1个数都不重复,取值在[1, n+1],并把每一个数看做一个节点;
    2. 则除了0和n+1外,所有的节点的入度为1(下标[0, n]),出度也为1(取值[1, n+1]且不重复);
    3. 0的出度为1,入度为0,n+1的入度为1,出度为0;
    4. 按照以上的推导可知,此时所有节点必定是以0为头节点,以n+1为尾节点的有向无环图;
    5. 将n+1节点的入度换到[1, n]的任一节点中,则必定会出现环,且该节点就是所求的重复整数;
    如果有多个重复值,则:
    6. 在上述基础上令某个节点的入度为0,重复值节点增加一个入度,相当于断开某个节点重连;
    7. 在环外断开则环内不变,环外路径变短,在环内断开则环外不变,环内路径变短;
    8. 此时必定仍有且只有一个环,只是某些节点可能无法从0开始遍历到;
    9. 但环是一定可以从0开始遍历到的;
    因此,可以转换为求有向有环图的首个入环节点问题,用快慢指针来求解,0是伪头节点;
    快慢指针求入环点的推导如下:
    1. fs = ss + n*circle = 2*ss;
    => ss = n*circle;
    2. ss = a(环外) + b(环内);
    => ss再走a就可以凑够整环回到入环处,从头走a也可以到入环处;
    */
    int findDuplicate(vector<int>& nums) {
    
    
        int fast = 0, slow = 0;
        while(fast==0 || fast!=slow) {
    
    
            slow = nums[slow];
            fast = nums[fast];
            fast = nums[fast];
        }
        int slow2 = 0;
        while(slow != slow2) {
    
    
            slow = nums[slow];
            slow2 = nums[slow2];
        }
        return slow;
    }
};

Nine, bit operation

1. A number that appears only once

topic description

  • Ideas :
  • What is used is the nature of the XOR operation, that is x xor x = 0, x xor 0 = x;
  • Therefore, it is enough to XOR from the beginning to the end, and the same number can be eliminated by XOR;
  • In addition, there are similar types of advanced questions, see 9.2. The number of times numbers appear in the array I and 9.3. The number of times numbers appear in the array II in Jianzhi offer algorithm question 02 ;
  • code :
class Solution {
    
    
public:
    int singleNumber(vector<int>& nums) {
    
    
        int result = 0;
        for(int i=0;i<nums.size();++i) {
    
    
            result ^= nums[i];
        }
        return result;
    }
};

2. Hamming distance

topic description

  • Ideas :
  • Is the XOR operation;
  • In addition, when counting the number of 1 in the XOR result, you can use it x = x & (x-1)to speed up the efficiency;
  • code :
class Solution {
    
    
public:
    int hammingDistance(int x, int y) {
    
    
        int n = x ^ y;
        int re = 0;
        // 统计异或结果中1的个数
        while(n != 0) {
    
    
            ++re;
            n = n & (n - 1);
        }
        return re;
    }
};

Ten, double pointer

[1]. The longest substring without repeated characters

topic description

  • Ideas :
  • Use double pointers to locate substrings;
  • Use hash map to record whether elements are repeated, and can record the subscript that appeared last time, so that the pointer can imove quickly;
  • It is the same question as 7.7. The longest substring without repeated characters in Jianzhi offer algorithm question 02 , but it is classified as dynamic programming (essentially still a double pointer), and you can find any substring starting from the beginning The longest substring without repeated characters, but at the same time, it needs to be saved in the dp array with the same length as the substring. This approach uses a variable to save the maximum value, and the space complexity is O(1). And the idea of ​​​​double pointers is easier to think of.
  • code :
class Solution {
    
    
public:
    /*
    双指针:
        i指向子串前一位,j指向字串后一位
        因此子串长度 = j - i - 1
    使用unordered_map的时候注意:
        若出现了map[key]的形式,则无论是读取还是写入,都将会为不存在的key创建
        之后的map.find将返回true
        所以测试输出时应当小心使用printf("%d\n", map[key]);
    */
    int lengthOfLongestSubstring(string s) {
    
    
        unordered_map<char, int> map;
        int i = -1, j = 0;
        int re = 0;
        while(j < s.length()) {
    
    
            if(map.find(s[j])!=map.end() && (map[s[j]]>=i && map[s[j]]<j)) {
    
    
                // 更新长度
                re = max(re, j - i - 1);
                // 移动指针i
                i = map[s[j]];                
            }
            map[s[j]] = j;
            // 移动指针j
            ++j;
        }
        // 最后一个无重复子串也要判断
        re = max(re, j - i - 1);
        return re;
    }
};

2. The container that holds the most water

topic description

  • Ideas :
  • The core point is that you only need to move the short board each time , because moving the long board will definitely make the area smaller;

train of thought

  • code :
class Solution {
    
    
public:
    int maxArea(vector<int>& height) {
    
    
        int i = 0, j = height.size() - 1;
        int re_max = 0;
        while(i < j) {
    
          
            // 用短板的高度 * 宽度     
            int area = min(height[i], height[j]) * (j - i);
            // 记录面积最大值
            if(area > re_max) {
    
    
                re_max = area;
            } 
            // 将短板往中间移动
            if(height[i] > height[j]) {
    
    
                --j;
            }
            else {
    
    
                ++i;
            }
        }
        return re_max;
    }
};
Variant 1. Rainwater

topic description

  • In fact, I am not sure whether this question can be regarded as a variant of 2. The container that holds the most water , but it is indeed possible to compare the two questions;
  • Idea 1: Forward and reverse traversal
  • The key to solving this problem is: only consider ithe rainwater that can be received, not the shape of the container on both sides;
  • Therefore, ithe rainwater that can be received at the place = the lower side of the maximum container height on both sides of it - height[i];
  • In order to get ithe maximum height of the containers on both sides, you can traverse forward to get the maximum value of the left container, and reversely traverse to get the maximum value of the right container;
  • The time complexity is O(3N) (can be optimized to O(2N)), and the space complexity is O(2N);

train of thought

  • Code one :
class Solution {
    
    
public:
    /*
    正向反向遍历:
    1. 对于每个height[i]而言,它能够接到的雨水(仅考虑在i上,不考虑它两边的实际形状)
        取决于它两边最大高度的更低一方 - height[i]
    2. 因此正向遍历得left_max[i],反向遍历得right_max[i]即可
    */
    int trap(vector<int>& height) {
    
    
        vector<int> left_max(height.size(), 0);
        vector<int> right_max(height.size(), 0);

        // 正向遍历
        for(int i=0;i<height.size();++i) {
    
    
            left_max[i] = height[i];
            if(i-1>=0 && left_max[i]<left_max[i-1]) {
    
    
                left_max[i] = left_max[i-1];
            }
        }
        // 逆向遍历
        for(int i=height.size()-1;i>=0;--i) {
    
    
            right_max[i] = height[i];
            if(i+1<height.size() && right_max[i]<right_max[i+1]) {
    
    
                right_max[i] = right_max[i+1];
            }
        }
        // 计算接雨水之和
        int re_sum = 0;
        for(int i=0;i<height.size();++i) {
    
    
            re_sum += min(left_max[i], right_max[i]) - height[i];
        }
        return re_sum;
    }
};
  • Idea 2: Double pointer

  • But in fact, the optimal solution to this question is to use double pointers . Although it is not intuitive, the essential idea is also an improvement on the first idea ;

  • Through double pointers, two traversal solutions to the left_max[i]sum are avoided ;right_max[i]

  • Because when calculating the amount of rainwater, it is not necessary to find all the sums left_max[i], right_max[i]but only the smaller value between them , which provides a space for optimization (but it is still a very clever double pointer, hard to imagine);

  • See the comment section of the code below for some derivation process;

  • The time complexity is reduced to O(N), and the space complexity is reduced to O(1);

  • Code two :

class Solution {
    
    
public:
    /*
    双指针:
    1. 对于每个height[i]而言,它能够接到的雨水(仅考虑在i上,不考虑它两边的实际形状)
        取决于min(left_max[i], right_max[i]) - height[i]
    实际上,可以用双指针巧妙地替代计算left_max[i]和right_max[i]的两次遍历
    2. 定义左指针i,右指针j,i及之前的最大值left_max,j及之后的最大值right_max
    3. 对于i而言:
        left_max[i] = left_max;
        right_max[i] >= right_max;
        故若left_max < right_max,则必有left_max[i]<right_max[i]
    4. 同理,对于j而言:
        left_max[j] >= left_max;
        right_max[j] = right_max;
        故若left_max > right_max,则必有left_max[j]>right_max[j]
    5. 如此可以计算出所有位置两边最大高度更低的一方,交替移动指针即可
    */
    int trap(vector<int>& height) {
    
    
        int i = 0, j = height.size() - 1;
        int left_max = height[i], right_max = height[j];
        int re_sum = 0;
        while(i != j) {
    
    
            if(left_max < right_max) {
    
    
            	// i处的雨水可算,移动左指针
                re_sum += (left_max - height[i]);
                ++i;
                left_max = max(left_max, height[i]);
            }
            else {
    
    
            	// j处的雨水可算,移动右指针
                re_sum += (right_max - height[j]);
                --j;
                right_max = max(right_max, height[j]);
            }
        }
        return re_sum;
    }
};

3. The sum of three numbers

topic description

  • Ideas :

  • Using double pointers after sorting can take the complexity from O ( N 3 ) O(N^3)O ( N3 )down toO( N 2 ) O(N^2)O ( N2)

  • It can be regarded as an upgraded version of the sum of two numbers. Refer to the ten, 4. and two numbers of s in Jianzhi offer algorithm question 02 , but the judgment of the number of repetitions is added;

  • Judgment of duplicate numbers is difficult, but it can be eliminated by sorting and skipping consecutive identical numbers without using a hash table (it is also difficult for hash to judge the repeatability of three numbers at the same time);
    train of thought

  • code :

class Solution {
    
    
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
    
    
        // 排序
        sort(nums.begin(), nums.end());
        int k = 0;
        vector<vector<int>> re;
        while(k < nums.size()) {
    
    
            if(nums[k] > 0) {
    
    
                // 剪枝,提前终止
                break;
            }
            // 固定k之后使用双指针
            int i = k + 1, j = nums.size() - 1;
            while(i < j) {
    
    
                int sum = nums[k] + nums[i] + nums[j];
                if(sum == 0) {
    
    
                    vector<int> temp(3, 0);
                    temp[0] = nums[k];
                    temp[1] = nums[i];
                    temp[2] = nums[j];
                    re.push_back(temp);
                    ++i;
                    // 跳过重复值
                    while(i<j && nums[i]==nums[i-1]) {
    
    
                        ++i;
                    }
                }
                if(sum < 0) {
    
    
                    // 和不够大
                    ++i;
                    // 跳过重复值
                    while(i<j && nums[i]==nums[i-1]) {
    
    
                        ++i;
                    }
                }
                if(sum > 0) {
    
    
                    // 和太大
                    --j;
                    // 跳过重复值
                    while(i<j && nums[j]==nums[j+1]) {
    
    
                        --j;
                    }
                }
            }
            ++k;
            // 跳过重复值
            while(k<nums.size() && nums[k]==nums[k-1]) {
    
    
                ++k;
            }
        }
        return re;
    }
};

4. Delete the last N node of the linked list

topic description

  • Ideas :
  • Just use two pointers that are separated by n nodes one after the other, so that when the latter pointer arrives nullptr, the previous pointer is exactly the penultimate nth node;
  • But because the node is to be deleted, the previous pointer should actually point to the previous node of the penultimate nth node;
  • In addition, special consideration should be given to the case where the penultimate n-th node is the first node (head node), that is to say, it cannot point to the previous node of the penultimate n-th node. Of course, this special head node processing can be done by adding a dummy Head node avoid;
  • code :
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
    
    
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
    
    
        ListNode *i = head, *j = head;
        int count = 0;  // i和j相距的节点数
        while(j != nullptr) {
    
    
            j = j->next;
            ++count;
            if(count > n + 1) {
    
    
                i = i->next;
            }
        }
        if(count == n) {
    
    
            // i是要删除的节点
            head = i->next;
            delete i;
        }
        else {
    
    
            // i->next是要删除的节点
            if(i->next != nullptr) {
    
    
                // 因为count>0,所以i->next必不为空,故不用else
                ListNode *tmp = i->next;
                i->next = tmp->next;
                delete tmp;
            }
        }
        return head;
    }
};
  • Here's how it's handled using pseudo-header nodes:
class Solution {
    
    
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
    
    
    	// 增加伪头节点
        ListNode *fakeHead = new ListNode();
        fakeHead->next = head;

        ListNode *i = fakeHead, *j = fakeHead;
        int count = 0;  // i和j相距的节点数
        while(j != nullptr) {
    
    
            j = j->next;
            ++count;
            if(count > n + 1) {
    
    
                i = i->next;
            }
        }
        // i->next是要删除的节点
        if(i->next != nullptr) {
    
    
            // 因为count>0,所以i->next必不为空,故不用else
            ListNode *tmp = i->next;
            i->next = tmp->next;
            delete tmp;
        }        
        return fakeHead->next;
    }
};

5. Color classification

topic description

  • Ideas :
  • The time complexity of direct sorting is O ( N log N ) O(NlogN)O(NlogN)
  • But if you do it with double pointer, the time complexity is O ( N ) O(N)O ( N ) , because there are only three elements in total;
  • If there are more than three elements, you cannot use double pointers;

train of thought

  • Need to pay attention to the following aspects:

    • To ensure that next0the pointer does not point to element 0 and next2the pointer does not point to element 2, it can whilebe used to achieve;
    • After curexchanging elements with two pointers, if curthe element pointed to after the exchange is not 1, the element still needs to continue processing;
    • The way to continue processing can be implemented with a rollback pointer;
    • In fact, the purpose of processing is to let curthe pointed element be 1, and the other two elements are exchanged to the head and tail respectively;
  • code :

class Solution {
    
    
public:
    /*
    三指针
    1. next0指向连续0的下一个位置,从左到右遍历
    2. next2指向连续2的前一个位置,从右到左遍历
    3. cur指向处理的当前位置,从左到右遍历
    一些规律如下:
    1. next0指向的数一定是1或者2
    2. next2指向的数一定是0或者1
    3. 如果nums[cur] == 1,则不需要交换
    4. 如果nums[cur] == 0或者2,则交换到next0或者next2
    5. 注意交换后的数如果是1,则不需要进一步处理,否则需要回退cur指针重复处理
    */
    void sortColors(vector<int>& nums) {
    
    
        int next0 = 0, next2 = nums.size() - 1;
        int cur = 0;
        while(cur < nums.size()) {
    
    
            if(nums[cur] == 0) {
    
    
                while(next0 < cur && nums[next0] == 0) {
    
    
                    ++next0;
                }
                if(next0 < cur) {
    
    
                    // next0在cur前面
                    swap(nums[cur], nums[next0]);
                    if(nums[cur] == 2) {
    
    
                        // cur回退
                        --cur;
                    }
                }
            }
            else {
    
    
                while(next2 > cur && nums[next2] == 2) {
    
    
                    --next2;
                }
                if(next2 > cur) {
    
    
                    // next2在cur后面
                    swap(nums[cur], nums[next2]);
                    if(nums[cur] == 0) {
    
    
                        // cur回退
                        --cur;
                    }
                }
            }
            ++cur;
        }
    }
};

6. Minimum Covering Substring [Sliding Window]

topic description

  • Ideas :

  • A sliding window with two pointers is used, that is, the left and right pointers can only move from left to right;

  • The handling of boundary conditions and special cases is cumbersome { { {(>_<)}}};
    train of thought

  • The main points are as follows :

  • Two hash tables are required, one to record the characters and the number of occurrences of a small string, and the other to record the characters and the number of occurrences in a large string of sliding windows;

  • When moving a sliding window in a large string:

    • First move the right pointer until the sliding window can cover all the characters and times of the small string;
    • Move the left pointer again until the sliding window just cannot cover all the characters and times of the small string;
  • Note that the left pointer can coincide with the right pointer when moving;

  • code :

class Solution {
    
    
public:
    /*
    1. 移动right,直到全部全部字符均能覆盖
    2. 移动left,直到某个字符不能覆盖
    3. 记录此时的right - left
    */
    string minWindow(string s, string t) {
    
    
        unordered_map<char, int> t_map, s_map;
        int min_length = s.length() + 1;
        string re;
        // 初始化两个map
        // t_map用于记录, 后续不再修改;s_map用于计数,后续修改
        for(int i=0;i<t.length();++i) {
    
    
            if(t_map.find(t[i]) == t_map.end()) {
    
    
                t_map[t[i]] = 1;
                s_map[t[i]] = 0;  // s_map仅初始化
            }
            else {
    
    
                t_map[t[i]] += 1;
            }
        }
        int left = 0, right = 0;
        int count = 0;
        // 在s上移动滑动窗口
        while(right < s.length()) {
    
    
            if(t_map.find(s[right]) != t_map.end()) {
    
    
                // t中含有右指针字符
                s_map[s[right]] += 1;
                if(s_map[s[right]] == t_map[s[right]]) {
    
    
                    // 符合条件的字符计数 + 1
                    ++count;
                } 
            }
            if(count == t_map.size()) {
    
    
                // 全部找齐了
                while(left <= right) {
    
    
                    if(t_map.find(s[left]) != t_map.end()) {
    
    
                        // t中含有左指针字符
                        s_map[s[left]] -= 1;
                        // 检验是否已不能覆盖
                        if(s_map[s[left]] < t_map[s[left]]) {
    
    
                            --count;
                            // 记录最小子串
                            if(min_length > right-left+1) {
    
    
                                min_length = right-left+1;
                                re = s.substr(left, min_length);                            
                            }
                            // break前记得再移动一次左指针
                            ++left;
                            break;
                        }
                    }                    
                    // 移动左指针
                    ++left;
                }
            }
            // 移动右指针
            ++right;
        }
        return re;
    }
};

7. Ring linked list

topic description

  • Ideas :
  • Of course, you can use hash set to do it, just check whether this point has already appeared after each point;
  • But this question can be judged by a very clever speed and slow double pointer;
    train of thought
  • The main points are as follows:
    • First of all, it is necessary to judge the situation of no node and only one node , and it is impossible for them to have a ring;
    • The initial positions of the fast and slow pointers are on headthe nodes, and the fast pointer takes one step more than the slow pointer each time;
    • Note that the condition for the end of the loop is that one of the fast and slow pointers is empty ;
  • The double pointer method will be much faster than using hash set, because there is no need to put elements into the set and determine whether there are duplicate elements in the set;
  • code :
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
    
    
public:
    bool hasCycle(ListNode *head) {
    
            
        if(head==nullptr || head->next == nullptr) {
    
    
            // 没有节点或仅有一个节点,则不可能成环
            return false;
        }
        ListNode *fast = head;
        ListNode *slow = head;

        while(fast!=nullptr && slow!=nullptr) {
    
    
            slow = slow->next;
            fast = fast->next;
            if(fast != nullptr) {
    
    
                // fast指针每次多走一步
                fast = fast->next;
            }
            if(slow == fast) {
    
    
                return true;
            }
        }
        return false;
    }
};
Variant 1. Circular Linked List II

topic description

  • Ideas :
  • The increased difficulty lies in the need to return the first node that enters the ring, rather than just judging whether there is a ring;
  • Of course, you can use hash set to do it, which is exactly the same as simply judging whether there is a ring, but the space complexity is O(N);
  • If it is a double pointer, it is a little more complicated and requires two encounters:
    • If you can meet for the first time, it means that there is a ring;
    • But it still needs an encounter to find the first node entering the ring;
    • Simply put, it is to put another slowpointer from headthe starting point until it meets the current slowpointer;
    • The derivation process is as follows:

The derivation process

  • Some popular explanations for why slowthe pointer can meet with the pointer within the first circle of entering the ring :fast

popular explanation

  • Note that the above nmeans that from the perspective of the ring, the current step slowis in fastfront n;
  • code :
class Solution {
    
    
public:
    /*
    a:从head到第一个环节点经过的节点
    b:一个环的节点
    第一次相遇的时候,fast比slow多走n圈,有:
    slow: s
    fast: f = 2*s = s + nb    
    得s = nb
    如果要到第一个环节点,需要走:a + nb
    因此slow再走a个节点就到第一个环节点,这也正好是从head到第一个环节点距离
    所以还需要一个new_slow从head开始与slow第二次相遇
    */
    ListNode *detectCycle(ListNode *head) {
    
    
        if(head == nullptr || head->next==nullptr) {
    
    
            // 空节点或者只有一个节点都不可能成环
            return nullptr;
        }
        ListNode *fast = head;
        ListNode *slow = head;
        while(fast!=nullptr && slow!=nullptr) {
    
    
            slow = slow->next;
            fast = fast->next;
            if(fast != nullptr) {
    
    
                fast = fast->next;  // fast多走一步
            }
            if(slow == fast) {
    
    
            	// 存在环,找第一个入环的节点
                fast = head;  // fast充当new_slow
                while(fast != slow) {
    
    
                    // 做第二次相遇
                    fast = fast->next;
                    slow = slow->next;
                }
                return slow;
            }
        }
        return nullptr;
    }
};

[8]. Intersecting list

topic description

  • Ideas :
  • It is the same question as 3. The first common node of two linked lists in Question 02 of Jianzhi offer algorithm ;
  • The two pointers start from two respectively head, and jump to the other if they are empty head;
  • The key is that the two pointers must be the same at the end , no matter whether there is a common node or not;
  • code :
class Solution {
    
    
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
    
    
        ListNode *pa = headA, *pb = headB;
        while(pa != pb) {
    
    
            if(pa != nullptr) {
    
    
                pa = pa->next;
            }
            else {
    
    
                pa = headB;
            }
            if(pb != nullptr) {
    
    
                pb = pb->next;
            }
            else {
    
    
                pb = headA;
            }
        }
        return pa;
    }
};

9. Moving Zero

topic description

  • Ideas :

  • Of course, you can use an idea similar to bubble sorting, and the time complexity is O(NlogN);

  • But it is more ingenious to use double pointers , and the time complexity can be reduced to O(N);

    • First, move the two pointers to the first 0position, at this time, the left side is non- 0number;
    • Then move the right pointer to the right, and 0exchange with the left pointer when encountering a non-number;
    • When the right pointer reaches the end, if the left pointer has not reached the end, let it reach the end, and set all the numbers in the moving process to 0;
    • Traverse each number at most twice in the whole process;
      train of thought
      train of thought
  • code :

class Solution {
    
    
public:
    void moveZeroes(vector<int>& nums) {
    
    
        int p1 = 0, p2 = 0;
        while(p1 < nums.size() && nums[p1] != 0) {
    
    
            // p1移动到第一个0处,左侧全为非0
            ++p1;
        }
        p2 = p1;
        while(p2 < nums.size()) {
    
          
            // p2继续向右走,遇到非0则和p1交换值     
            if(nums[p2] != 0) {
    
    
                nums[p1] = nums[p2];
                ++p1;
            }
            ++p2;
        }
        while(p1 < nums.size()) {
    
    
            // 将剩余的值填0
            nums[p1] = 0;
            ++p1;
        }
    }
};

10. Find all anagrams in a string [sliding window]

topic description

  • Ideas :
  • Use double pointers to form a sliding window. If the characters in the sliding window can just cover the matching characters, just record the subscript;
  • How to judge that the characters in the sliding window just cover the matched characters?
  • Two hash tables are needed here , one is used to count the number of characters matched by the target cnt_map, and the other is used to count the number of characters matched by the current sliding window map;
  • How to move the double pointer?
  • Classification discussion is required here, and the discussion of boundaries is cumbersome, assuming left pointers iand right pointers j:
    • (1) If s[j]it is a character in p and map[s[j]]it is not full, then put the character in mapp, and ++j;
    • At this time, if there is mapa complete match cnt_map, it is necessary ++ito mapmake room for matching;
    • (2) If s[j]it is a character in p, but map[s[j]]it is already full, then move the left pointer, and at the same s[i]time maptake it out until mapit can be put down s[j];
    • (3) If s[j]it is not a character in p, move the left pointer and right pointer j+1everywhere (because s[j]the substring before and after must not meet the condition), and take out the jprevious s[i]onemap during the process of moving the left pointer;
  • code :
class Solution {
    
    
public:
    vector<int> findAnagrams(string s, string p) {
    
    
        unordered_map<char, int> cnt_map, map;  // cnt_map用来统计p中字符出现的次数        
        for(char c:p) {
    
    
            ++cnt_map[c];
        }

        vector<int> re;
        int rest_num = p.length(); 
        int i = 0, j = 0;
        while(j < s.length()) {
    
               
            if(cnt_map[s[j]]>0 && map[s[j]]<cnt_map[s[j]]) {
    
    
                // 情况1:从map中填入p字符                
                ++map[s[j]];
                --rest_num;

                // 完整重排p
                if(rest_num == 0) {
    
    
                    re.push_back(i);
                    // 放字符放回map中
                    --map[s[i]];
                    ++rest_num;
                    // 移动左指针
                    ++i;
                }
                // 移动右指针
                ++j;
            }
            else {
    
    
                if(cnt_map[s[j]] == 0) {
    
    
                    // 情况3:p中不存在这个字符
                    while(i < j) {
    
    
                        // 放字符放回map中
                        --map[s[i]];
                        ++rest_num;
                        // 移动左指针
                        ++i;
                    }
                    ++i;
                    ++j;
                }
                else {
    
    
                    // 情况2:p中有这个字符但map放不下
                    while(s[i] != s[j]) {
    
    
                        --map[s[i]];
                        ++rest_num;
                        ++i;
                    }
                    ++i;
                    ++j;
                }
            }
        }
        return re;
    }
};

11. Shortest unordered contiguous subarray

topic description

  • Ideas :
  • In fact, it is a bit greedy + double pointer twice;
  • The schematic diagram including disorder is as follows:

schematic diagram

  • Then the numbers in the out-of-order interval have the following characteristics, and the bold words indicate the greedy rule:
    • Scanning from left to right, the right boundary of disorder must be smaller than the maximum value currently found;
    • Because normal numbers are in ascending order from left to right, the number that matches the ascending order must be greater than or equal to the current maximum value;
    • Recording the last out-of-order number is the right boundary of the out-of-order interval;
    • Scanning from right to left, the left boundary of the random order must be greater than the currently found minimum value;
    • Because normal numbers are in descending order from right to left, the numbers that match the descending order must be less than or equal to the current minimum value;
    • Recording the last out-of-order number is the left boundary of the out-of-order interval;
  • Therefore, two rounds of scanning can be performed to find the left and right boundaries of the disordered interval;
  • The reason why it is called a double pointer is because a pointer is needed to traverse each round of finding the boundary , and a pointer is recorded in the back to record the last out-of-sequence number ;
  • Of course, because the lengths of the two rounds of scans are the same, it is also possible to combine the two rounds of scans into one round of scans;
  • Note that if there is an out-of-order interval, the length of the out-of-order interval is at least 2 ;
  • code :
class Solution {
    
    
public:
    int findUnsortedSubarray(vector<int>& nums) {
    
    
        int left = 0, right = -1;
        int max_num = INT_MIN, min_num = INT_MAX;
        for(int i=0;i<nums.size();++i) {
    
    
            if(max_num <= nums[i]) {
    
    
                // nums[i]越来越大,表明是正常升序
                max_num = nums[i];
            }
            else {
    
    
                // nums[i]是乱序,更新乱序的右边界
                right = i;
            }
        }
        for(int i=nums.size()-1;i>=0;--i) {
    
    
            if(min_num >= nums[i]) {
    
    
                // nums[i]越来越小,表明是正常降序
                min_num = nums[i];
            }
            else {
    
    
                // nums[i]是乱序,更新乱序的左边界
                left = i;
            }
        } 
        // 如果right == left,则表明无乱序,因为不会存在长度为1的乱序
        // 如果right != left,则表明存在乱序,且乱序的长度必定大于等于2
        return right - left + 1;
    }
};

Eleven, heap

1. The Kth largest element in the array [quick sort search]

topic description

  • Ideas :

  • Of course , you can use a heap to do the same kas finding all the previous elements. For reference: Jianzhi offer algorithm question 02 , Eleven . Define the sorting function, the time complexity is O(Nlogk);

  • With general sorting, the time complexity is at least O(NlogN);

  • In addition, no matter whether it is to find the first kelement or the previous kelement, it can also be solved by a quick sort method . After adding the random strategy , the average time complexity is O(N), and the worst is O(N^2) if it is not added. , which is slower than using the heap method;

  • Here we will implement the class quick sort search method , and the implementation process is as follows:

    • Randomly select [low, high]an element in between pivot, and exchange it nums[low]everywhere , instead of directly selecting nums[low]as pivot, which can avoid the worst time complexity is O(N^2);
    • Then according to the idea of ​​quick sort, move pivotto the middle position, the values ​​on the left are greater than pivot, and the values ​​on the right are less than pivot;
    • If the current pivotsubscript is k-1, then directly return its value which is the first kelement, because the subscript starts from 0;
    • If pivotthe subscript iis less than k-1, only search [i+1,high], otherwise, only search [low, i-1], that is, compared to quicksort, only one side is searched;
  • Some points to note:

    • Since pivotit starts from nums[low]the beginning, the movement of the pointer should move first j, from highto the left ;
    • There should be two layers of loops when implementing while, until iit jmeets the pointer;
    • Every whilesum ifjudgment should have i<jthis condition;
    • At the end, you need to remember to pivotreassign;
    • If you want to implement the function yourself swap, you can directly use reference + temporary variable ;
  • code :

class Solution {
    
    
private:
    int re;
    void my_swap(int &a, int &b) {
    
    
        // 用引用+临时变量即可
        int temp = a;
        a = b;
        b = temp;
    }
    void quickFind(vector<int>& nums, int low, int high, int& k) {
    
    
        if(low > high) {
    
    
            // 和quickSort不一样,等号的时候也要走一遍,不然i==k-1可能无法取得
            return;
        }
        int i = low, j = high;

        // 引入随机选择pivot,能够避免最坏为O(n^2),整体是O(n)
        int rand_index = low + rand()%(high-low+1);
        my_swap(nums[low], nums[rand_index]);

        // 往下是快排的写法
        int pivot = nums[low];        
        while(i < j) {
    
    
            // 把pivot放到中间,前大后小
            while(i<j && pivot>=nums[j]){
    
    
                --j;
            }
            if(i < j) {
    
    
                nums[i] = nums[j];  // nums[j] = pivot
                ++i;
            }
            while(i<j && pivot<=nums[i]) {
    
    
                ++i;
            }
            if(i < j) {
    
    
                nums[j] = nums[i];  // nums[i] = pivot
                --j;
            }
        }       
        // 重新赋值
        nums[i] = pivot;

        if(i == k-1) {
    
    
            re = nums[i];
            return;
        }
        else {
    
    
            // k-1在[low, i-1]中
            if(i > k-1) {
    
     quickFind(nums, low, i-1, k); }
            // k-1在[i+1, high]中
            if(i < k-1) {
    
     quickFind(nums, i+1, high, k); }            
        }
    }
public:
    int findKthLargest(vector<int>& nums, int k) {
    
    
        quickFind(nums, 0, nums.size()-1, k);
        return re;
    }
};

2. Top K high-frequency elements [quick sort search]

Problem Description

  • Ideas :
  • It is almost the same question as Eleven, 1. The smallest k number in Question 02 of Jianzhi offer algorithm ;
  • It is to find the previous knumber with the highest frequency;
  • There are two special features, or increased difficulty:
    • (1) It is necessary to count the frequency of appearance first , instead of directly using the value of the array;
    • The countermeasure is unordered_mapto traverse the statistics once;
    • (2) It is necessary to use a small top pile , but the standard priority_queueis a large top pile, which needs further modification;
    • The countermeasure is to customize the sorting function, or use a quick-sort method instead of a heap;
  • Idea 1: Class Quick Search
  • A similar implementation to Quick Sort, but only one side can be searched;
  • The points to note are as follows:
    • An additional quickFind()function needs to be defined. Note that the return type must also be the same voidtype as the quick sort. The parameters include arrays arr, subscripts low, highand search positions k, which are implemented recursively;
    • The subscript passed in initially must be a compact bound , which can be obtained in the array;
    • The recursive termination condition is different from the quick sort, the scope is smaller and cannot be included low == high;
    • When using whilethe moving pointer, pay attention to the need to move when it is equal to ;
    • Be sure to use a random selectionpivot strategy, otherwise the expectation of time complexity cannot reach O(N);
    • The condition for finding the result is that i == k-1if you use kand, k == arr.size()you cannot find it, because kit is not within the subscript range of the array at this time;
  • code :
class Solution {
    
    
private:
    vector<int> re;
    void quickFind(vector<pair<int, int>>& arr, int low, int high, int k) {
    
    
        // 等号也必须取到
        if(low > high) {
    
    
            return;
        }        

        // 取随机数,[low, high]间有high-low+1个元素,1要加上
        int rand_index = low + rand()%(high - low + 1);
        swap(arr[low], arr[rand_index]);

        pair<int, int> pivot = arr[low];
        int i = low, j = high;
        while(i < j) {
    
    
            // 注意等于号
            while(i<j && arr[j].second<=pivot.second) {
    
    
                --j;
            }
            if(i<j) {
    
    
                arr[i] = arr[j];
                ++i;
            }
            // 注意等于号
            while(i<j && arr[i].second>=pivot.second) {
    
    
                ++i;
            }
            if(i<j) {
    
    
                arr[j] = arr[i];
                --j;
            }
        }
        arr[i] = pivot;

        // 一定要i==k-1,而不能i==k,否则如果k==arr.size()则是找不出的
        if(i == k-1) {
    
    
            //printf("k=%d\n", i);
            for(int index=0;index<=i;++index) {
    
    
                re.push_back(arr[index].first);
            }
            return;
        }
        else {
    
    
            if(i < k) {
    
    
                quickFind(arr, i+1, high, k);
            }
            else {
    
    
                quickFind(arr, low, i-1, k);
            }
            return;
        }
    }
public:
    vector<int> topKFrequent(vector<int>& nums, int k) {
    
    
        unordered_map<int, int> count_map;
        // 统计出现的次数
        for(int i=0;i<nums.size();++i) {
    
    
            if(count_map.find(nums[i]) == count_map.end()) {
    
    
                count_map[nums[i]] = 0;
            }
            ++count_map[nums[i]];
        }

        // 转存到数组
        vector<pair<int, int>> count_arr;
        for(auto i=count_map.begin();i!=count_map.end();++i) {
    
    
            count_arr.push_back({
    
    i->first, i->second});
        }

        // 类快排查找
        // 注意上下界均是紧确界
        quickFind(count_arr, 0, count_arr.size()-1, k);

        return re;
    }
};
  • Idea 2: Small top pile
  • Of course, it can also be done with a small top heap , and the time complexity is slightly higher, but the actual running time is not necessarily much higher;
  • Find the largest kelement and use the small top heap ;
  • Find the smallest kelement and use the big top heap ;
  • There are two ways to customize the comparison function;
  • (1) Implemented with a functor :
  • This method is the implementation method of the STL standard and is recommended;
  • Refer to cpp official documentation and blog: priority queue (priority_queue) – custom sorting ;
  • code :
class Solution {
    
    
private:
    // 仿函数
    struct cmp {
    
    
        bool operator() (const pair<int, int>& a, const pair<int, int>& b) {
    
    
            return a.second > b.second;
        }
    };
public:
    vector<int> topKFrequent(vector<int>& nums, int k) {
    
    
        unordered_map<int, int> count_map;
        // 统计出现的次数
        for(int i=0;i<nums.size();++i) {
    
    
            if(count_map.find(nums[i]) == count_map.end()) {
    
    
                count_map[nums[i]] = 0;
            }
            ++count_map[nums[i]];
        }

        // 转存到小顶堆
        priority_queue<pair<int, int>, vector<pair<int, int>>, cmp> heap;
        for(auto i=count_map.begin();i!=count_map.end();++i) {
    
    
            if(heap.size() < k) {
    
    
                heap.push({
    
    i->first, i->second});
            }
            else {
    
    
                pair<int, int> tmp = heap.top();
                if(i->second > tmp.second) {
    
    
                    heap.pop();
                    heap.push({
    
    i->first, i->second});
                }
            }
        }

        // 记录到数组
        vector<int> re;
        while(!heap.empty()) {
    
    
            re.push_back(heap.top().first);
            heap.pop();
        }

        return re;
    }
};
  • (2) Implemented with ordinary function pointers:
  • This way of writing is given by the official solution of leetcode, and it seems to be the way of writing in the new standard of C++11;
  • A function is used decltype()to return the type of the incoming parameter;
  • Note cmpthat it is a static function;
  • code :
class Solution {
    
    
private:
    static bool cmp(const pair<int, int>& a, const pair<int, int>& b) {
    
    
        return a.second > b.second;
    }
public:
    vector<int> topKFrequent(vector<int>& nums, int k) {
    
    
        unordered_map<int, int> count_map;
        // 统计出现的次数
        for(int i=0;i<nums.size();++i) {
    
    
            if(count_map.find(nums[i]) == count_map.end()) {
    
    
                count_map[nums[i]] = 0;
            }
            ++count_map[nums[i]];
        }

        // 转存到小顶堆
        priority_queue<pair<int, int>, vector<pair<int, int>>, decltype(&cmp)> heap(cmp);
        for(auto i=count_map.begin();i!=count_map.end();++i) {
    
    
            if(heap.size() < k) {
    
    
                heap.push({
    
    i->first, i->second});
            }
            else {
    
    
                pair<int, int> tmp = heap.top();
                if(i->second > tmp.second) {
    
                       
                    heap.pop();
                    heap.push({
    
    i->first, i->second});
                }
            }
        }

        // 记录到数组
        vector<int> re;
        while(!heap.empty()) {
    
    
            re.push_back(heap.top().first);
            heap.pop();
        }

        return re;
    }
};
  • In addition, sortfunctions cmpcan use function pointers or functors, but when using functors, the T()form is not used T. In fact, the parameters passed in are still function pointers instead of class objects. Refer to the blog: C++ functors and Summary of custom sort functions ;
Supplement: Custom cmp about priority_queue
  • It is recommended to use the form of functor;
  • The functor is defined as follows:
// 仿函数
struct cmp {
    
    
    bool operator() (const T& a, const T& b) {
    
    
        return a严格小于b的条件;
    }
};
  • priority_queueis defined as follows:
priority_queue<T, vector<T>, cmp> heap;
  • There are three descriptions of parameter types;
  • Because priority_queueit is vectoran adapter, the type to be explicitly given vector;
  • No cmp(), but directly pass in the entire functor class type cmp;
  • If it is a standard type, for example , you can also directly use the standard functor intin STL , the usage is as follows:greater
priority_queue<int, vector<int>, greater<int>> heap;

12. The Law of Greed

1. Jumping Game

topic description

  • Ideas :

  • I originally planned to use dynamic programming to do it, but the time complexity of dynamic programming is O ( N 2 ) O(N^2)O ( N2 ), because for eachijudgmentdp[i]whether it is reachable, it is necessary to traverse the previous position to see if there is a chance to reach iti;

  • In fact, just use the greedy method directly, and the time complexity is O ( N ) O(N)O ( N )

  • Core points :

    • traverse from front to back;
    • Record the farthest subscript that is currently reachable ;
    • Once the traversal exceeds the farthest subscript, it terminates;
    • Finally, if the farthest subscript is greater than or equal to the length of the array, it means that the entire array is reachable, and returns true;
  • In addition, be careful not to exceed the length of the array when traversing;
    train of thought

  • code :

class Solution {
    
    
public:
    bool canJump(vector<int>& nums) {
    
    
        int max_reach = 0;
        int cur = 0;
        while(cur <= max_reach && cur < nums.size()) {
    
    
        	// 仅当cur处于最大可达距离内才遍历
            max_reach = max(cur + nums[cur], max_reach);
            ++cur;
        }
        if(max_reach >= nums.size()-1) {
    
    
            return true;
        }
        else {
    
    
            return false;
        }
    }
};

2. Combine intervals

topic description

  • Ideas :

  • Sort first, then traverse and merge sequentially;

  • merge [ L 1 , R 1 ][L_1,R_1][L1,R1] and[ L 2 , R 2 ] [L_2,R_2][L2,R2] when:

    • If R 1 >= L 2 R_1>=L_2R1>=L2, the two intervals can be merged;
    • If R 1 > R 2 R_1>R_2R1>R2, then the merged interval is [ L 1 , R 1 ] [L_1,R_1][L1,R1] , otherwise[ L 1 , R 2 ] [L_1,R_2][L1,R2]
    • If it cannot be merged, try to merge the next interval;
  • It can be proved that if sorting first and then processing in this way, a certain interval that can be merged will definitely not be missed, the proof is as follows;
    proving process

  • code :

class Solution {
    
    
public:
    // 必须是static,因为是成员函数,不用static的话不能在sort中使用
    // 但如果不是成员函数的话就不需要用static
    // 传入的参数用引用的话时间和空间都能极大地节省
    static bool cmp(vector<int>& a, vector<int>& b) {
    
    
        if(a[0] < b[0]) {
    
    
            return true;
        }
        if(a[0] > b[0]) {
    
    
            return false;
        }
        else {
    
    
            if(a[1] < b[1]) {
    
    
                return true;
            }
            else {
    
    
                return false;
            }
        }
    }

    vector<vector<int>> merge(vector<vector<int>>& intervals) {
    
    
        sort(intervals.begin(), intervals.end(), cmp);
        vector<vector<int>> re;
        int i = 0;
        while(i < intervals.size()) {
    
    
            vector<int> tmp = intervals[i];
            ++i;
            while(i < intervals.size() && tmp[1] >= intervals[i][0]) {
    
    
                // 注意比较前一个区间的右区间和下一个区间的右区间的大小
                tmp[1] = max(tmp[1], intervals[i][1]);
                ++i;
            }
            re.push_back(tmp);
        }
        return re;
    }
};
Supplement: Custom cmp about the sort function
  • booltype return value;
  • Pass in two parameters aand bshould be decorated with constand references &to reduce space and time overhead (very important!);
  • For truethe representative ain the frontb row;
  • If it is a member function, it should be modified with static+ private, otherwise it is not needed;
  • vectorand stringtypes can be sortsorted with;
  • An example is shown above;
  • Special attention is : the value truemust be strictly less than , =and the value cannot be returned true, otherwise the recursive process may fall into an infinite loop or empty recursion when there are equal values;

3. Reconstruct the queue based on height

topic description

  • Ideas :
  • There are two constraints [h_i, k_i];
  • The greedy method is implemented as follows:
    • First h_isort according to from high to low;
    • Then traverse each person in turn and insert it into the first k_iposition from the front to the back;
  • Why does the greedy method work?
    • First of all, for the first person traversed i, there must be k_i <= i, that is to say, the insertion operation is to insert into the queue that has been arranged before;
    • (1) For the first iperson, such an insertion must meet the conditions, because the people in front are all taller than it, so it k_idepends ;
    • (2) For the people who have been arranged before, because the first iperson is shorter than them, no matter iwhere the first person is inserted, their original k_ivalues ​​will not be changed, so the condition is also met;
    • So every time a person is processed, the number of people who meet the conditions will be increased by one, and the result will be after the traversal;
  • code :
  • The insertion operation is best listperformed with a container;
  • Note that the use of container iterators and values(*i) ​​needs to be parenthesized, and taking a pointeri-> is equivalent to taking it first (*i)and then (*i)->;
  • forThe termination condition of the loop is !=container.end():
  • list.insert()The first parameter of is an iterator, which must be list.begin()obtained through auto-increment (iterator auto-increment has been overloaded);
  • Moreover, listthe bottom layer is a two-way circular linked list, which itself does not support the logic of random reading;
  • Of course, if it is vectoran iterator, because its space is inherently continuous, and the iterator is essentially a normal pointer, so the vector.begin() + intform can also be used;
class Solution {
    
    
private:
    static bool cmp(const vector<int>& a, const vector<int>& b) {
    
    
        if(a[0] > b[0]) {
    
    
            return true;
        }
        else {
    
    
            if(a[0] == b[0] && a[1] < b[1]) {
    
    
                return true;
            } 
        }
        return false;
    }
public:
    vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
    
    
        // 排序people
        sort(people.begin(), people.end(), cmp);

        list<vector<int>> re_list;
        for(auto i=people.begin();i!=people.end();++i) {
    
    
            // 顺序遍历,并依次把每个元素插入到第k_i个位置
            int tmp = (*i)[1];
            auto tmp_it = re_list.begin();
            while(--tmp >= 0) {
    
    
                ++tmp_it;
            }
            re_list.insert(tmp_it, *i);  // insert函数是在position之前插入

            // print_info
            // for(auto j=re_list.begin();j!=re_list.end();++j) {
    
    
            //     printf("[%d, %d], ",(*j)[0], (*j)[1]);
            // }
            // printf("\n");
        }

        vector<vector<int>> re;
        for(auto i=re_list.begin();i!=re_list.end();++i) {
    
    
            re.push_back(*i);
        }

        return re;
    }
};

4. Task Scheduler

topic description

  • Ideas :
  • Of course, you can simulate the entire scheduling process according to the idea of ​​the topic, and each time you choose the task that is not within the waiting time and has the most remaining execution times to execute;
  • But in fact, a two-dimensional matrix (bucket) can be constructed, and the shortest execution time can be obtained by discussing this matrix, as follows:

train of thought
train of thought
train of thought

  • See the comment section of the code below for some of your own derivation process;
  • code :
class Solution {
    
    
public:
    /*
    贪心法:
    1. 先找出执行次数最多的任务,记录次数为k,如果有并列最多的,记录并列数为x
    2. 构建一个k行n+1列的矩阵,且最后一行仅有x个任务,这样:
        (1) 只要每一行内的任务类型不重复,则不会发生有任务处于待命状态而冲突
        (2) 相同类型的任务填入不同行也不会发生冲突
        (3) 如果矩阵填不满,则所需要的最短时间不会变,仍然是填满矩阵的时间
        (4) 如果矩阵填满而且超出,则每一行可以增加列来填入
            也就是能够安排一种方案让所有任务都不需要等待执行
    3. 则完成任务的时间为:
        (1) (k-1) * (n+1) + x, if 填不满或刚好填满
        (2) tasks.size(), if 超出
    */
    int leastInterval(vector<char>& tasks, int n) {
    
    
        // 桶计数
        vector<int> bucket_count(26, 0);
        int k = 0;  // 执行次数最多的任务的执行次数
        int x = 0;  // 有并列最多的任务的并列数
        for(int i=0;i<tasks.size();++i) {
    
    
            ++bucket_count[tasks[i]-'A'];
            if(bucket_count[tasks[i]-'A'] > k) {
    
    
                k = bucket_count[tasks[i]-'A'];
                x = 1;
            }
            else {
    
    
                if(bucket_count[tasks[i]-'A'] == k) {
    
    
                    ++x;
                }
            }
        }

        // 注意size()返回的是unsigned_int类型,需要做类型转换
        return max(int(tasks.size()), (k - 1) * (n + 1) + x);
    }
};

Thirteen, figure

1. Curriculum [Directed Acyclic Graph]

topic description

  • Ideas :

  • The essence is to judge whether the graph is a directed acyclic graph;

  • Equivalent to whether a topological sort can be obtained from the graph;

  • Some introductions about topological sorting:
    topological sort

  • The topological sort method is as follows:
    topological sort

  • In essence, it is to constantly find 0the nodes with the current in-degree and then release their in-degree;

    • Maintaining in-degree is achieved 0through queues , so it is equivalent to breadth-first traversal;
    • Be careful not to use loops to find, otherwise the time complexity is O(N^2), and the queue only needs O(N);
  • The data structures used include:

    • Two-dimensional vector: record the output node corresponding to each node;
      • Note that it is not a record entry node;
      • Because if you only record in-nodes, you have to traverse all nodes to delete in-degrees anyway;
      • If the node is recorded, you only need to traverse all the nodes of the node;
      • In this way, the time complexity is reduced to O(e), that is, all edges are traversed again;
    • One-dimensional vector: record the in-degree of each node;
      • Because the node whose in-degree is to be deleted 0, the in-degree value must be recorded again;
  • The time complexity of this implementation is the lowest, and it is relatively easy to understand;

  • code :

  • Queue implementation version:

class Solution {
    
    
public:
    /*
    1. 本质上是判断当前有向图中是否存在环
    2. 可以转换成是否能够从有向图中获得一个拓扑排序
        如果可以获得,则无环,反之则有环;
    3. 拓扑排序是:
        1) 每次取入度为0的节点
        2) 直至全部取完,则无环;
        3) 或无入度为0的节点但未取完,则有环;
    */
    bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
    
    
        vector<vector<int>> out_nodes(numCourses);
        vector<int> in_num(numCourses, 0);

        for(auto p: prerequisites) {
    
    
            out_nodes[p[1]].emplace_back(p[0]);
            ++in_num[p[0]];
        } 

        queue<int> q;
        for(int i=0;i<numCourses;++i) {
    
    
            if(in_num[i] == 0) {
    
    
                q.emplace(i);
            }
        }

        vector<int> re;
        while(!q.empty()) {
    
    
            int delete_node = q.front();
            q.pop();
            re.emplace_back(delete_node);

            // 遍历所有出节点,删除它们的入度
            for(auto out_node: out_nodes[delete_node]) {
    
    
                --in_num[out_node];
                if(in_num[out_node] == 0) {
    
    
                    q.emplace(out_node);
                }
            }
        }

        if(re.size() == numCourses) {
    
    
            return true;
        }
        else {
    
    
            return false;
        }
    }
};
Supplement: Method for judging whether there is a cycle in undirected graphs and directed graphs

Judgment loop method

  • Topological sorting is recommended for directed graphs ;
  • The recommended mathematical method for undirected graphs :
    • If it is acyclic, the maximum number of edges can only be the number of nodes -1 ;
    • If the number of edges exceeds the number of nodes -1, it means that there must be a ring;
    • Judgment is simpler than directed graph;
Variant 1. Curriculum II

topic description

  • Ideas :

  • The idea is exactly the same as that of the curriculum , and it is also topologically sorted;

  • It just needs to record the order of topological sorting and then return;

  • code :

class Solution {
    
    
public:
    /*
    1. 本质上是判断当前有向图中是否存在环
    2. 可以转换成是否能够从有向图中获得一个拓扑排序
        如果可以获得,则无环,反之则有环;
    3. 拓扑排序是:
        1) 每次取入度为0的节点
        2) 直至全部取完,则无环;
        3) 或无入度为0的节点但未取完,则有环;
    */
    vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) 
    {
    
    
        vector<vector<int>> out_nodes(numCourses);
        vector<int> in_num(numCourses, 0);

        for(int i=0;i<prerequisites.size();++i) {
    
    
            // 记录每个节点的出节点
            out_nodes[prerequisites[i][1]].emplace_back(prerequisites[i][0]);
            // 记录每个节点的入度
            ++in_num[prerequisites[i][0]];
        }

        queue<int> q;
        for(int i=0;i<numCourses;++i) {
    
    
            if(in_num[i] == 0) {
    
    
                q.emplace(i);
            }
        }
    
        vector<int> re;
        while(!q.empty()) {
    
    
            int delete_node = q.front();
            re.emplace_back(delete_node);
            q.pop();

            // 对每个出节点的入度减一
            for(int i=0;i<out_nodes[delete_node].size();++i) {
    
    
                int out_node = out_nodes[delete_node][i];
                --in_num[out_node];
                if(in_num[out_node] == 0) {
    
    
                    q.emplace(out_node);
                }
            }
        }

        if(re.size() == numCourses) {
    
    
            return re;
        }
        else {
    
    
            return {
    
    };
        }
    }
};

Other types:

1. Most elements

topic description

class Solution {
    
    
public:
    /*摩尔投票法*/
    int majorityElement(vector<int>& nums) {
    
    
        int major;
        int votes = 0;
        for(int i=0;i<nums.size();++i) {
    
    
            if(votes == 0) {
    
    
                major = nums[i];
                ++votes;
            }
            else {
    
    
                if(major == nums[i]) {
    
    
                    ++votes;
                }
                else {
    
    
                    --votes;
                }
            }
        }
        return major;
    }
};

[2]. The product of arrays other than itself

topic description

  • Ideas :
  • It is to traverse twice to get a list of prefix products and a list of suffix products ;
  • Then perform another traversal, and the result can be obtained by multiplying the product of the prefix and the suffix ;
  • Of course, it can also be traversed twice or even once, but the subscript processing will be more complicated;
  • It is the same question as other types in Jianzhi offer algorithm question 02 , 4. Constructing a product array ;
  • code :
class Solution {
    
    
public:
    vector<int> productExceptSelf(vector<int>& nums) {
    
    
        int n = nums.size();

        vector<int> pre_multipy(nums.size(), 0);  // 到i的前向乘积(含i)
        vector<int> post_multipy(nums.size(), 0);  // 到j的后向乘积(含j)
        
        // 计算前向乘积
        pre_multipy[0] = nums[0];
        for(int i=1;i<n;++i) {
    
    
            pre_multipy[i] = pre_multipy[i-1] * nums[i];
        }
        // 计算后向乘积
        post_multipy[n-1] = nums[n-1];
        for(int i=n-2;i>=0;--i) {
    
    
            post_multipy[i] = post_multipy[i+1] * nums[i];
        }
        
        // 计算结果
        vector<int> re(n, 0);
        re[0] = post_multipy[1];
        re[n-1] = pre_multipy[n-2];
        for(int i=1;i<n-1;++i) {
    
    
            re[i] = pre_multipy[i-1] * post_multipy[i+1];
        }
        return re;
    }
};

Guess you like

Origin blog.csdn.net/weixin_43992162/article/details/127286890