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


    LeetCode 41. 缺失的第一个正数(困难)

    【题目描述】

    给你一个未排序的整数数组 nums,请你找出其中没有出现的最小的正整数
    请你实现时间复杂度为 O ( n ) O(n) O(n) 并且只使用常数级别额外空间的解决方案。

    【示例1】

    输入:nums = [1,2,0]
    输出:3
    
    • 1
    • 2

    【示例2】

    输入:nums = [3,4,-1,1]
    输出:2
    
    • 1
    • 2

    【示例3】

    输入:nums = [7,8,9,11,12]
    输出:1
    
    • 1
    • 2

    【提示】

    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全部没有缺失
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    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 个单位的雨水(蓝色部分表示雨水)。
    
    • 1
    • 2
    • 3

    【示例2】

    输入:height = [4,2,0,3,2,5]
    输出:9
    
    • 1
    • 2

    【提示】

    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

    【分析】


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

    在这里插入图片描述

    我们开一个栈记为 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;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    LeetCode 43. 字符串相乘(中等)

    【题目描述】

    给定两个以字符串形式表示的非负整数 num1num2,返回 num1num2 的乘积,它们的乘积也表示为字符串形式。
    注意:不能使用任何内置的 BigInteger 库或直接将输入转换为整数。

    【示例1】

    输入: num1 = "2", num2 = "3"
    输出: "6"
    
    • 1
    • 2

    【示例2】

    输入: num1 = "123", num2 = "456"
    输出: "56088"
    
    • 1
    • 2

    【提示】

    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;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    LeetCode 44. 通配符匹配(困难)

    【题目描述】

    给你一个输入字符串(s)和一个字符模式(p),请你实现一个支持 '?''*' 匹配规则的通配符匹配:

    • '?' 可以匹配任何单个字符。
    • '*' 可以匹配任意字符序列(包括空字符序列)。

    判定匹配成功的充要条件是:字符模式必须能够完全匹配输入字符串(而不是部分匹配)。

    【示例1】

    输入:s = "aa", p = "a"
    输出:false
    解释:"a" 无法匹配 "aa" 整个字符串。
    
    • 1
    • 2
    • 3

    【示例2】

    输入:s = "aa", p = "*"
    输出:true
    解释:'*' 可以匹配任意字符串。
    
    • 1
    • 2
    • 3

    【示例3】

    输入:s = "cb", p = "?a"
    输出:false
    解释:'?' 可以匹配 'c', 但第二个 'a' 无法匹配 'b'。
    
    • 1
    • 2
    • 3

    【提示】

    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];
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    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 步到达数组的最后一个位置。
    
    • 1
    • 2
    • 3

    【示例2】

    输入: nums = [2,3,0,1,4]
    输出: 2
    
    • 1
    • 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];
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
  • 相关阅读:
    PHP namespace(命名空间) 和 use; 很多人搞不清楚命名空间和使用方法,书上介绍也不清楚看着头大
    记一次MySQL执行修改语句超时问题
    docker(Kubernetes)环境如何查看network namespace
    Druid 任意文件读取 (CVE-2021-36749)
    车载网络测试 - UDS诊断篇 - 流控制帧
    合宙AIR105(四): SPI, MAX7219 8x8LED驱动
    计算机体系结构:MIPS计算例题(1.7)
    线程纵横:C++并发编程的深度解析与实践
    『吴秋霖赠书活动 | 第三期』《Python asyncio并发编程》
    OpenGL进阶(二)之像素缓冲PixelBuffer
  • 原文地址:https://blog.csdn.net/m0_51755720/article/details/132618789