【LeetCode算法系列题解】第41~45题

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 1nums.length5105
− 2 31 ≤ n u m s [ i ] ≤ 2 31 − 1 -2^{31}\le nums[i]\le 2^{31} - 1 231nums[i]2311

【分析】


注意本题的时空复杂度,不能用排序以及哈希表之类的做法。

我们可以用置换的方法求解,我们记 n n n n u m s nums nums 的长度,因此答案一定在 1 ∼ n + 1 1\sim n+1 1n+1 之间(因为最完美的情况是 n u m num num 中有 1 ∼ n 1\sim n 1n)。

我们利用两两交换的方法将 n u m num num 中在 1 ∼ n 1\sim n 1n 范围内的数放置到其对应的位置上,也就是 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】

【LeetCode算法系列题解】第41~45题_第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 1height.length2104
0 ≤ h e i g h t [ i ] ≤ 1 0 5 0\le height[i]\le 10^5 0height[i]105

【分析】


本题需要用到单调栈的思想,先画出示意图:

【LeetCode算法系列题解】第41~45题_第2张图片

我们开一个栈记为 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. 字符串相乘(中等)

【题目描述】

给定两个以字符串形式表示的非负整数 num1num2,返回 num1num2 的乘积,它们的乘积也表示为字符串形式。
注意:不能使用任何内置的 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 1num1.length,num2.length200
num1num2 只能由数字组成
num1num2 都不包含任何前导零,除了数字0本身

【分析】


使用高精度乘法的处理方式,假设两个数的低到高位的下标分别为 0 ∼ n 0\sim n 0n 0 ∼ m 0\sim m 0m,那么两个数的第 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 0s.length,p.length2000
s 仅由小写英文字母组成
p 仅由小写英文字母、'?''*' 组成

【分析】


本题与第10题的区别在于这边的 * 可以匹配任意字符串,而第10题的 * 表示可以某个字符可以为任意长度。

同样的,我们来分析一下状态表示和状态计算:

  • 状态表示 f [ i ] [ j ] f[i][j] f[i][j]
    • 集合:所有 s [ 1 ∼ i ] s[1\sim i] s[1i] p [ 1 ∼ j ] p[1\sim j] p[1j](下标从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 i1 个字符和 j j j 的前 j − 1 j-1 j1 个字符也匹配,那么 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 j1(此处与第10题有区别)个字符匹配;如果表示1个字符,则 s s s 的前 i − 1 i-1 i1 个字符和 j j j 的前 j − 1 j-1 j1 个字符匹配;如果表示2个字符,则 s s s 的前 i − 2 i-2 i2 个字符和 j j j 的前 j − 1 j-1 j1 个字符匹配。因此可以写出以下状态转移方程:
      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]

本题的 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 1nums.length104
0 ≤ n u m s [ i ] ≤ 1000 0\le nums[i]\le 1000 0nums[i]1000
题目保证可以到达 nums[n - 1]

【分析】


f[i] 表示起点跳到 i i i 的最短步数,那么 f[i] 一定是单调递增的,且相邻差值一定不会超过1,假设 i < j ii<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];
    }
};

你可能感兴趣的:(LeetCode,算法,leetcode,职场和发展,c++,动态规划)