CONTENTS
- LeetCode 41. 缺失的第一个正数(困难)
- LeetCode 42. 接雨水(困难)
- LeetCode 43. 字符串相乘(中等)
- LeetCode 44. 通配符匹配(困难)
- LeetCode 45. 跳跃游戏 II(中等)
 
LeetCode 41. 缺失的第一个正数(困难)
【题目描述】
给你一个未排序的整数数组 nums,请你找出其中没有出现的最小的正整数。
 请你实现时间复杂度为  
     
      
       
       
         O 
        
       
         ( 
        
       
         n 
        
       
         ) 
        
       
      
        O(n) 
       
      
    O(n) 并且只使用常数级别额外空间的解决方案。
【示例1】
输入:nums = [1,2,0]
输出:3
【示例2】
输入:nums = [3,4,-1,1]
输出:2
【示例3】
输入:nums = [7,8,9,11,12]
输出:1
【提示】
 
     
      
       
       
         1 
        
       
         ≤ 
        
       
         n 
        
       
         u 
        
       
         m 
        
       
         s 
        
       
         . 
        
       
         l 
        
       
         e 
        
       
         n 
        
       
         g 
        
       
         t 
        
       
         h 
        
       
         ≤ 
        
       
         5 
        
       
         ∗ 
        
       
         1 
        
        
        
          0 
         
        
          5 
         
        
       
      
        1\le nums.length\le 5 * 10^5 
       
      
    1≤nums.length≤5∗105
  
     
      
       
       
         − 
        
        
        
          2 
         
        
          31 
         
        
       
         ≤ 
        
       
         n 
        
       
         u 
        
       
         m 
        
       
         s 
        
       
         [ 
        
       
         i 
        
       
         ] 
        
       
         ≤ 
        
        
        
          2 
         
        
          31 
         
        
       
         − 
        
       
         1 
        
       
      
        -2^{31}\le nums[i]\le 2^{31} - 1 
       
      
    −231≤nums[i]≤231−1
【分析】
注意本题的时空复杂度,不能用排序以及哈希表之类的做法。
我们可以用置换的方法求解,我们记 n n n 为 n u m s nums nums 的长度,因此答案一定在 1 ∼ n + 1 1\sim n+1 1∼n+1 之间(因为最完美的情况是 n u m num num 中有 1 ∼ n 1\sim n 1∼n)。
我们利用两两交换的方法将  
     
      
       
       
         n 
        
       
         u 
        
       
         m 
        
       
      
        num 
       
      
    num 中在  
     
      
       
       
         1 
        
       
         ∼ 
        
       
         n 
        
       
      
        1\sim n 
       
      
    1∼n 范围内的数放置到其对应的位置上,也就是 nums[i] = i + 1。但其中有若干个位置上的数是错误的,每一个错误的位置就代表了一个缺失的正数。以 [3, 4, -1, 1] 为例,置换后的数组应当为 [1, -1, 3, 4],我们就可以知道缺失的数为  
     
      
       
       
         2 
        
       
      
        2 
       
      
    2。
注意在交换的过程中应判断两数是否相等防止死循环。
【代码】
class Solution {
public:
    int firstMissingPositive(vector<int>& nums) {
        int n = nums.size();
        for (int i = 0; i < n; i++)
            // 将当前数置换到其对应的位置num[i]-1
            while (nums[i] > 0 && nums[i] <= n && nums[i] != nums[nums[i] - 1])
                swap(nums[i], nums[nums[i] - 1]);
        for (int i = 0; i < n; i++)
            if (nums[i] != i + 1) return i + 1;  // 该位置上没有对应的正整数说明缺失了
        return n + 1;  // 1~n全部没有缺失
    }
};
LeetCode 42. 接雨水(困难)
【题目描述】
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
【示例1】

输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。
【示例2】
输入:height = [4,2,0,3,2,5]
输出:9
【提示】
 
     
      
       
       
         1 
        
       
         ≤ 
        
       
         h 
        
       
         e 
        
       
         i 
        
       
         g 
        
       
         h 
        
       
         t 
        
       
         . 
        
       
         l 
        
       
         e 
        
       
         n 
        
       
         g 
        
       
         t 
        
       
         h 
        
       
         ≤ 
        
       
         2 
        
       
         ∗ 
        
       
         1 
        
        
        
          0 
         
        
          4 
         
        
       
      
        1\le height.length\le 2 * 10^4 
       
      
    1≤height.length≤2∗104
  
     
      
       
       
         0 
        
       
         ≤ 
        
       
         h 
        
       
         e 
        
       
         i 
        
       
         g 
        
       
         h 
        
       
         t 
        
       
         [ 
        
       
         i 
        
       
         ] 
        
       
         ≤ 
        
       
         1 
        
        
        
          0 
         
        
          5 
         
        
       
      
        0\le height[i]\le 10^5 
       
      
    0≤height[i]≤105
【分析】
本题需要用到单调栈的思想,先画出示意图:

我们开一个栈记为 stk,每根柱子的编号已在图中显示,假设柱子的高度依次为:height = [6, 3, 0, 5, 4, 5, 7],算法流程如下:
- 栈为空,将 0入栈(当前栈:[0]);
- 1小于栈顶元素,入栈(当前栈:- [0, 1]);
- 2小于栈顶元素,入栈(当前栈:- [0, 1, 2]);
- 3大于等于栈顶元素,将小于等于- 3的元素依次出栈,首先出栈- 2,若栈里还有元素,则- res += (3 - 1 - 1) * (min(height[1], height[3]) - height[2]) = 3(红色区域),其中- 1为栈顶元素;然后出栈- 1,栈不为空,- res += (3 - 1 - 0) * (min(height[0], height[3]) - height[1]) = 7(黄色区域);栈顶元素- 0大于- 3,停止出栈,将- 3入栈(当前栈:- [0, 3]);
- 4小于栈顶元素,入栈(当前栈:- [0, 3, 4]);
- 5大于等于栈顶元素,将小于等于- 5的元素依次出栈,首先出栈- 4,栈不为空,- res += (5 - 1 - 3) * (min(height[3], height[5]) - height[4]) = 8(绿色区域);然后出栈- 3,栈不为空,- res += (5 - 1 - 0) * (min(height[0], height[5]) - height[3]) = 8(面积为0);栈顶元素- 0大于- 5,停止出栈,将- 5入栈(当前栈:- [0, 5]);
- 6大于等于栈顶元素,将小于等于- 6的元素依次出栈,首先出栈- 5,栈不为空,- res += (6 - 1 - 0) * (min(height[0], height[6]) - height[5]) = 13(蓝色区域);然后出栈- 0,栈为空,停止出栈,将- 6入栈(当前栈:- [6]),遍历结束。
【代码】
class Solution {
public:
    int trap(vector<int>& height) {
        stack<int> stk;
        int res = 0;
        for (int i = 0; i < height.size(); i++)
        {
            if (stk.empty() || height[i] < height[stk.top()]) { stk.push(i); continue; }
            while (stk.size() && height[i] >= height[stk.top()])
            {
                int t = stk.top(); stk.pop();
                if (stk.size())  // 注意判断栈里是否还有元素
                    res += (i - 1 - stk.top()) * (min(height[stk.top()], height[i]) - height[t]);
            }
            stk.push(i);
        }
        return res;
    }
};
LeetCode 43. 字符串相乘(中等)
【题目描述】
给定两个以字符串形式表示的非负整数 num1 和 num2,返回 num1 和 num2 的乘积,它们的乘积也表示为字符串形式。
 注意:不能使用任何内置的 BigInteger 库或直接将输入转换为整数。
【示例1】
输入: num1 = "2", num2 = "3"
输出: "6"
【示例2】
输入: num1 = "123", num2 = "456"
输出: "56088"
【提示】
 
     
      
       
       
         1 
        
       
         ≤ 
        
       
         n 
        
       
         u 
        
       
         m 
        
       
         1. 
        
       
         l 
        
       
         e 
        
       
         n 
        
       
         g 
        
       
         t 
        
       
         h 
        
       
         , 
        
       
         n 
        
       
         u 
        
       
         m 
        
       
         2. 
        
       
         l 
        
       
         e 
        
       
         n 
        
       
         g 
        
       
         t 
        
       
         h 
        
       
         ≤ 
        
       
         200 
        
       
      
        1\le num1.length, num2.length\le 200 
       
      
    1≤num1.length,num2.length≤200
 num1 和 num2 只能由数字组成
 num1 和 num2 都不包含任何前导零,除了数字0本身
【分析】
使用高精度乘法的处理方式,假设两个数的低到高位的下标分别为 0 ∼ n 0\sim n 0∼n 与 0 ∼ m 0\sim m 0∼m,那么两个数的第 i i i 位与第 j j j 位相乘后的结果将会累加到第 i + j i+j i+j 位上。我们可以遍历两个数先算出每一位上的乘积之和,最后在从低位到高位统一处理一遍进位即可。
【代码】
class Solution {
public:
    string multiply(string num1, string num2) {
        int n = num1.size(), m = num2.size();
        reverse(num1.begin(), num1.end());  // 字符串的最高位是数字的最低位
        reverse(num2.begin(), num2.end());
        vector<int> mul(n + m);
        for (int i = 0; i < n; i++)
            for (int j = 0; j < m; j++)
                mul[i + j] += (num1[i] - '0') * (num2[j] - '0');
        for (int i = 0, t = 0; i < n + m; i++)  // 统一处理进位
        {
            mul[i] += t;
            t = mul[i] / 10;
            mul[i] %= 10;
        }
        int k = n + m - 1;
        while (k && mul[k] == 0) k--;  // 去掉结果的前导0
        string res;
        while (k >= 0) res += mul[k--] + '0';
        return res;
    }
};
LeetCode 44. 通配符匹配(困难)
【题目描述】
给你一个输入字符串(s)和一个字符模式(p),请你实现一个支持 '?' 和 '*' 匹配规则的通配符匹配:
- '?'可以匹配任何单个字符。
- '*'可以匹配任意字符序列(包括空字符序列)。
判定匹配成功的充要条件是:字符模式必须能够完全匹配输入字符串(而不是部分匹配)。
【示例1】
输入:s = "aa", p = "a"
输出:false
解释:"a" 无法匹配 "aa" 整个字符串。
【示例2】
输入:s = "aa", p = "*"
输出:true
解释:'*' 可以匹配任意字符串。
【示例3】
输入:s = "cb", p = "?a"
输出:false
解释:'?' 可以匹配 'c', 但第二个 'a' 无法匹配 'b'。
【提示】
 
     
      
       
       
         0 
        
       
         ≤ 
        
       
         s 
        
       
         . 
        
       
         l 
        
       
         e 
        
       
         n 
        
       
         g 
        
       
         t 
        
       
         h 
        
       
         , 
        
       
         p 
        
       
         . 
        
       
         l 
        
       
         e 
        
       
         n 
        
       
         g 
        
       
         t 
        
       
         h 
        
       
         ≤ 
        
       
         2000 
        
       
      
        0\le s.length, p.length\le 2000 
       
      
    0≤s.length,p.length≤2000
 s 仅由小写英文字母组成
 p 仅由小写英文字母、'?' 或 '*' 组成
【分析】
本题与第10题的区别在于这边的 * 可以匹配任意字符串,而第10题的 * 表示可以某个字符可以为任意长度。
同样的,我们来分析一下状态表示和状态计算:
- 状态表示  
      
       
        
        
          f 
         
        
          [ 
         
        
          i 
         
        
          ] 
         
        
          [ 
         
        
          j 
         
        
          ] 
         
        
       
         f[i][j] 
        
       
     f[i][j]: 
  - 集合:所有 s [ 1 ∼ i ] s[1\sim i] s[1∼i] 和 p [ 1 ∼ j ] p[1\sim j] p[1∼j](下标从1开始)的匹配方案。
- 属性:bool类型,表示是否存在一个合法方案。
 
- 状态计算: 
  -  
        
         
          
          
            p 
           
          
            [ 
           
          
            j 
           
          
            ] 
           
          
            ≠ 
           
          
            ∗ 
           
          
         
           p[j]\ne * 
          
         
       p[j]=∗,那么直接看  
        
         
          
          
            s 
           
          
            [ 
           
          
            i 
           
          
            ] 
           
          
         
           s[i] 
          
         
       s[i] 和  
        
         
          
          
            p 
           
          
            [ 
           
          
            j 
           
          
            ] 
           
          
         
           p[j] 
          
         
       p[j] 是否匹配即可,若 s[i] == p[j]或者p[j] == '?',且满足 s s s 的前 i − 1 i-1 i−1 个字符和 j j j 的前 j − 1 j-1 j−1 个字符也匹配,那么 s [ i ] s[i] s[i] 和 p [ j ] p[j] p[j] 匹配,即可以写出以下状态转移方程:
 f[i][j] = f[i - 1][j - 1] && (s[i] == p[j] || p[j] == '?')
-  
        
         
          
          
            p 
           
          
            [ 
           
          
            j 
           
          
            ] 
           
          
            = 
           
          
            ∗ 
           
          
         
           p[j]=* 
          
         
       p[j]=∗,那么我们需要枚举一下 *表示多少个字符,如果表示0个字符,则 s s s 的前 i i i 个字符和 j j j 的前 j − 1 j-1 j−1(此处与第10题有区别)个字符匹配;如果表示1个字符,则 s s s 的前 i − 1 i-1 i−1 个字符和 j j j 的前 j − 1 j-1 j−1 个字符匹配;如果表示2个字符,则 s s s 的前 i − 2 i-2 i−2 个字符和 j j j 的前 j − 1 j-1 j−1 个字符匹配。因此可以写出以下状态转移方程:
 f[i][j] = f[i][j - 1] || f[i - 1][j - 1] || f[i - 2][j - 1] || ...
 现在我们进行优化(方式与第10题一模一样,也是完全背包优化方式),写出f[i - 1][j]的状态转移方程如下:
 f[i - 1][j] = f[i - 1][j - 1] || f[i - 2][j - 1] || ...
 因此可以写出优化后的状态转移方程:
 f[i][j] = f[i][j - 1] || f[i - 1][j]
 
-  
        
         
          
          
            p 
           
          
            [ 
           
          
            j 
           
          
            ] 
           
          
            ≠ 
           
          
            ∗ 
           
          
         
           p[j]\ne * 
          
         
       p[j]=∗,那么直接看  
        
         
          
          
            s 
           
          
            [ 
           
          
            i 
           
          
            ] 
           
          
         
           s[i] 
          
         
       s[i] 和  
        
         
          
          
            p 
           
          
            [ 
           
          
            j 
           
          
            ] 
           
          
         
           p[j] 
          
         
       p[j] 是否匹配即可,若 
本题的 f[0][j] 是有意义的,例如一堆 * 可以匹配空串;但是 f[i][0] 是没有意义的。
【代码】
class Solution {
public:
    bool isMatch(string s, string p) {
        int n = s.size(), m = p.size();
        s = ' ' + s, p = ' ' + p;
        vector<vector<bool>> f(n + 1, vector<bool>(m + 1));
        f[0][0] = true;
        for (int i = 0; i <= n; i++)
            for (int j = 1; j <= m; j++)
                if (p[j] != '*')
                    f[i][j] = i && f[i - 1][j - 1] && (s[i] == p[j] || p[j] == '?');  // 要特判i不为0防止越界
                else
                    f[i][j] = f[i][j - 1] || i && f[i - 1][j];  // 同样也要特判i不为0
        return f[n][m];
    }
};
LeetCode 45. 跳跃游戏 II(中等)
【题目描述】
给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]。
 每个元素 nums[i] 表示从索引 i 向前跳转的最大长度。换句话说,如果你在 nums[i] 处,你可以跳转到任意 nums[i + j] 处:
- 0 <= j <= nums[i]
- i + j < n
返回到达 nums[n - 1] 的最小跳跃次数。生成的测试用例可以到达 nums[n - 1]。
【示例1】
输入: nums = [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2。从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。
【示例2】
输入: nums = [2,3,0,1,4]
输出: 2
【提示】
 
     
      
       
       
         1 
        
       
         ≤ 
        
       
         n 
        
       
         u 
        
       
         m 
        
       
         s 
        
       
         . 
        
       
         l 
        
       
         e 
        
       
         n 
        
       
         g 
        
       
         t 
        
       
         h 
        
       
         ≤ 
        
       
         1 
        
        
        
          0 
         
        
          4 
         
        
       
      
        1\le nums.length\le 10^4 
       
      
    1≤nums.length≤104
  
     
      
       
       
         0 
        
       
         ≤ 
        
       
         n 
        
       
         u 
        
       
         m 
        
       
         s 
        
       
         [ 
        
       
         i 
        
       
         ] 
        
       
         ≤ 
        
       
         1000 
        
       
      
        0\le nums[i]\le 1000 
       
      
    0≤nums[i]≤1000
 题目保证可以到达 nums[n - 1]
【分析】
令 f[i] 表示起点跳到  
     
      
       
       
         i 
        
       
      
        i 
       
      
    i 的最短步数,那么 f[i] 一定是单调递增的,且相邻差值一定不会超过1,假设  
     
      
       
       
         i 
        
       
         < 
        
       
         j 
        
       
      
        i<j 
       
      
    i<j 且  
     
      
       
       
         f 
        
       
         [ 
        
       
         i 
        
       
         ] 
        
       
         > 
        
       
         f 
        
       
         [ 
        
       
         j 
        
       
         ] 
        
       
      
        f[i] > f[j] 
       
      
    f[i]>f[j],那么首先  
     
      
       
       
         j 
        
       
      
        j 
       
      
    j 一定不是从  
     
      
       
       
         i 
        
       
      
        i 
       
      
    i 跳过来的,因为这样  
     
      
       
       
         f 
        
       
         [ 
        
       
         j 
        
       
         ] 
        
       
         = 
        
       
         f 
        
       
         [ 
        
       
         i 
        
       
         ] 
        
       
         + 
        
       
         1 
        
       
      
        f[j]=f[i]+1 
       
      
    f[j]=f[i]+1,那么说明是在  
     
      
       
       
         i 
        
       
      
        i 
       
      
    i 之前的某个点跳过来的,那么从这个点跳到  
     
      
       
       
         i 
        
       
      
        i 
       
      
    i 后也至少能满足  
     
      
       
       
         f 
        
       
         [ 
        
       
         i 
        
       
         ] 
        
       
         = 
        
       
         f 
        
       
         [ 
        
       
         j 
        
       
         ] 
        
       
      
        f[i]=f[j] 
       
      
    f[i]=f[j],因此通过反证法即可证明该假设不成立。
因此我们可以找出 f[i] == x 的每一段,只需要在 f[i] == x - 1 的那一段中找出能够跳跃到的最远距离的点。可以枚举  
     
      
       
       
         i 
        
       
      
        i 
       
      
    i,由于  
     
      
       
       
         i 
        
       
      
        i 
       
      
    i 是单调递增的,如果  
     
      
       
       
         j 
        
       
      
        j 
       
      
    j 跳不到  
     
      
       
       
         i 
        
       
      
        i 
       
      
    i 那么更不可能跳到  
     
      
       
       
         i 
        
       
         + 
        
       
         1 
        
       
      
        i+1 
       
      
    i+1,因此  
     
      
       
       
         j 
        
       
      
        j 
       
      
    j 也是单调的,只需要枚举一遍即可。
【代码】
class Solution {
public:
    int jump(vector<int>& nums) {
        vector<int> f(nums.size());
        for (int i = 1, j = 0; i < nums.size(); i++)  // f[0]=0,从1开始
        {
            while (j + nums[j] < i) j++;  // 保证一定有解,因此肯定能从某个j跳到i
            f[i] = f[j] + 1;  // 从最左边的j跳到i
        }
        return f[nums.size() - 1];
    }
};



















