【LeetCode Solutions】LeetCode 151 ~ 155 题解

发布于:2025-04-13 ⋅ 阅读:(37) ⋅ 点赞:(0)

LeetCode 151. 反转字符串中的单词(中等)

【题目描述】

给你一个字符串 s,请你反转字符串中单词的顺序。

单词是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的单词分隔开。

返回单词顺序颠倒且单词之间用单个空格连接的结果字符串。

注意:输入字符串 s 中可能会存在前导空格、尾随空格或者单词间的多个空格。返回的结果字符串中,单词间应当仅用单个空格分隔,且不包含任何额外的空格。

【示例 1】

输入:s = "the sky is blue"
输出:"blue is sky the"

【示例 2】

输入:s = "  hello world  "
输出:"world hello"
解释:反转后的字符串中不能存在前导空格和尾随空格。

【示例 3】

输入:s = "a good   example"
输出:"example good a"
解释:如果两个单词间有多余的空格,反转后的字符串需要将单词间的空格减少到仅有一个。

【提示】

1 < = s . l e n g t h < = 1 0 4 1 <= s.length <= 10^4 1<=s.length<=104
s 包含英文大小写字母、数字和空格 ' '
s至少存在一个单词

进阶:如果字符串在你使用的编程语言中是一种可变数据类型,请尝试使用 O ( 1 ) O(1) O(1) 额外空间复杂度的原地解法。


【分析】

本题的思路也挺具有跳跃性,我们可以分解为以下两步操作:

  • 翻转整个字符串:例如 the sky is blue -> eulb si yks eht
  • 翻转每个单词:例如 eulb si yks eht -> blue is sky the

这两个操作是互相独立的,也可以先翻转每个单词然后再翻转整个字符串,这样代码写起来更方便一点,翻转的时候可以直接使用 reverse() 函数也可以用双指针实现。


【代码】

class Solution {
public:
    string reverseWords(string s) {
        int n = 0;  // 过滤无效空格后的字符串长度
        for (int i = 0; i < s.size(); i++) {
            if (s[i] == ' ') continue;
            int j = i;
            while (j < s.size() && s[j] != ' ') s[n++] = s[j++];
            reverse(s.begin() + n - (j - i), s.begin() + n);  // 将单词翻转
            s[n++] = ' ';  // 末尾统一先加上空格
            i = j;
        }
        n--;  // 去掉最后一个空格
        for (int l = 0, r = n - 1; l < r; l++, r--) swap(s[l], s[r]);  // 双指针翻转整个字符串
        s.erase(s.begin() + n, s.end());  // 删去无效的部分
        return s;
    }
};

LeetCode 152. 乘积最大子数组(中等)

【题目描述】

给你一个整数数组 nums,请你找出数组中乘积最大的非空连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。

测试用例的答案是一个 32 位整数。

【示例 1】

输入: nums = [2,3,-2,4]
输出: 6
解释: 子数组 [2,3] 有最大乘积 6。

【示例 2】

输入: nums = [-2,0,-1]
输出: 0
解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。

【示例 3】

输入: nums = [-2,3,-4]
输出: 24
解释: 子数组 [-2,3,-4] 有最大乘积 24。

【提示】

1 < = n u m s . l e n g t h < = 2 ∗ 1 0 4 1 <= nums.length <= 2 * 10^4 1<=nums.length<=2104
10 < = n u m s [ i ] < = 10 10 <= nums[i] <= 10 10<=nums[i]<=10
nums 的任何子数组的乘积都保证是一个 32 位整数


【分析】

  • 状态表示: f [ i ] f[i] f[i] 表示以 nums[i] 结尾的连续子区间的乘积最大值, g [ i ] g[i] g[i] 表示以 nums[i] 结尾的连续子区间的乘积最小值。
  • 状态计算:对于以 nums[i] 结尾的连续子区间,分以下几种情况:
    • 区间中只有 nums[i]:区间乘积的最大值与最小值都等于自身,即 f[i] = g[i] = nums[i]
    • 区间中包含以 nums[i - 1] 结尾的某个连续子区间:
      • nums[i] 大于 0:区间乘积的最大值应该是以 nums[i - 1] 结尾的连续子区间的乘积最大值再乘上自身,即 f[i] = f[i - 1] * nums[i];区间乘积的最小值应该是以 nums[i - 1] 结尾的连续子区间的乘积最小值再乘上自身,即 g[i] = g[i - 1] * nums[i]
      • nums[i] 小于 0:区间乘积的最大值应该是以 nums[i - 1] 结尾的连续子区间的乘积最小值再乘上自身,即 f[i] = g[i - 1] * nums[i];区间乘积的最小值应该是以 nums[i - 1] 结尾的连续子区间的乘积最大值再乘上自身,即 g[i] = f[i - 1] * nums[i]
      • nums[i] 等于 0:区间乘积无论如何都是 0,且这种情况可以算在区间只有 nums[i] 的情况中,因此也已经包含在内了。

由于 i i i 的状态只依赖于 i − 1 i - 1 i1,因此可以只用一个变量表示状态,这样空间复杂度就为 O ( 1 ) O(1) O(1),此外通过观察可以发现 f f f 最终一定就是在 nums[i]f[i - 1] * nums[i]g[i - 1] * nums[i] 这三种情况中取最大值,同理 g g g 也是,因此代码可以直接写在一起。


【代码】

【原始版本】

class Solution {
public:
    int maxProduct(vector<int>& nums) {
        int n = nums.size();
        vector<int> f(n), g(n);
        f[0] = nums[0], g[0] = nums[0];
        int res = nums[0];
        for (int i = 1; i < n; i++) {
            f[i] = g[i] = nums[i];  // 区间只有 nums[i] 的情况
            if (nums[i] > 0) f[i] = max(f[i], f[i - 1] * nums[i]), g[i] = min(g[i], g[i - 1] * nums[i]);
            else f[i] = max(f[i], g[i - 1] * nums[i]), g[i] = min(g[i], f[i - 1] * nums[i]);
            res = max(res, f[i]);
        }
        return res;
    }
};

【空间优化版】

class Solution {
public:
    int maxProduct(vector<int>& nums) {
        int f = nums[0], g = nums[0], res = nums[0];
        for (int i = 1; i < nums.size(); i++) {
            int tf = f;  // 更新 g 的时候 f 已经更新了,因此开个临时变量存一下 f
            f = max(nums[i], max(g * nums[i], f * nums[i]));
            g = min(nums[i], min(tf * nums[i], g * nums[i]));
            res = max(res, f);
        }
        return res;
    }
};

LeetCode 153. 寻找旋转排序数组中的最小值(中等)

【题目描述】

已知一个长度为 n n n 的数组,预先按照升序排列,经由 1 ∼ n 1 \sim n 1n旋转后,得到输入数组。例如,原数组 nums = [0,1,2,4,5,6,7] 在变化后可能得到:

  • 若旋转 4 次,则可以得到 [4,5,6,7,0,1,2]
  • 若旋转 7 次,则可以得到 [0,1,2,4,5,6,7]

注意,数组 [a[0], a[1], a[2], ..., a[n - 1]] 旋转一次的结果为数组 [a[n - 1], a[0], a[1], a[2], ..., a[n - 2]]

给你一个元素值互不相同的数组 nums,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的最小元素

你必须设计一个时间复杂度为 O ( l o g n ) O(log n) O(logn) 的算法解决此问题。

【示例 1】

输入:nums = [3,4,5,1,2]
输出:1
解释:原数组为 [1,2,3,4,5] ,旋转 3 次得到输入数组。

【示例 2】

输入:nums = [4,5,6,7,0,1,2]
输出:0
解释:原数组为 [0,1,2,4,5,6,7] ,旋转 4 次得到输入数组。

【示例 3】

输入:nums = [11,13,15,17]
输出:11
解释:原数组为 [11,13,15,17] ,旋转 4 次得到输入数组。

【提示】

n = = n u m s . l e n g t h n == nums.length n==nums.length
1 < = n < = 5000 1 <= n <= 5000 1<=n<=5000
− 5000 < = n u m s [ i ] < = 5000 -5000 <= nums[i] <= 5000 5000<=nums[i]<=5000
nums 中的所有整数互不相同
nums 原来是一个升序排序的数组,并进行了 1 ∼ n 1 \sim n 1n 次旋转


【分析】

LeetCode 33. 很相似,而且更简单,本题只需要二分一次就行。

如果数组还是有序的,那么一定满足最后一个数大于第一个数,这种情况下的最小值就是第一个数;如果不是有序的,说明数组由两段连续的递增序列组成,最小值为后一段的第一个数,最小值左边的部分(也就是前一段连续递增序列)一定大于等于 nums[0],自身以及右边的部分一定严格小于 nums[0],因此二分出严格小于 nums[0] 的第一个数就是最小值。


【代码】

class Solution {
public:
    int findMin(vector<int>& nums) {
        if (nums[nums.size() - 1] >= nums[0]) return nums[0];
        int l = 0, r = nums.size() - 1;
        while (l < r) {
            int mid = l + r >> 1;
            if (nums[mid] < nums[0]) r = mid;
            else l = mid + 1;
        }
        return nums[r];
    }
};

LeetCode 154. 寻找旋转排序数组中的最小值 II(困难)

【题目描述】

已知一个长度为 n n n 的数组,预先按照升序排列,经由 1 ∼ n 1 \sim n 1n旋转后,得到输入数组。例如,原数组 nums = [0,1,4,4,5,6,7] 在变化后可能得到:

  • 若旋转 4 次,则可以得到 [4,5,6,7,0,1,4]
  • 若旋转 7 次,则可以得到 [0,1,4,4,5,6,7]

注意,数组 [a[0], a[1], a[2], ..., a[n - 1]] 旋转一次的结果为数组 [a[n - 1], a[0], a[1], a[2], ..., a[n - 2]]

给你一个可能存在重复元素值的数组 nums,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的最小元素

你必须尽可能减少整个过程的操作步骤。

【示例 1】

输入:nums = [1,3,5]
输出:1

【示例 2】

输入:nums = [2,2,2,0,1]

【提示】

n = = n u m s . l e n g t h n == nums.length n==nums.length
1 < = n < = 5000 1 <= n <= 5000 1<=n<=5000
− 5000 < = n u m s [ i ] < = 5000 -5000 <= nums[i] <= 5000 5000<=nums[i]<=5000
nums 原来是一个升序排序的数组,并进行了 1 ∼ n 1 \sim n 1n 次旋转

进阶:这道题与上一题类似,但 nums 可能包含重复元素。允许重复会影响算法的时间复杂度吗?会如何影响,为什么?


【分析】

这题和上一题的区别就在于可能会有重复元素,这样就会导致后一段序列不一定严格小于 nums[0]。例如数组 [4, 5, 1, 4, 4, 4, 4, 4] 如果直接按上一题的方法二分就会二分到最后一个数 nums[7]

解决方法与 LeetCode 81. 一样,需要先把末尾与 nums[0] 相等的数先删去,这样就能保证二分的正确性。如果数组每个数都相同,那么最坏情况下时间复杂度就变为了 O ( n ) O(n) O(n)


【代码】

class Solution {
public:
    int findMin(vector<int>& nums) {
        while (nums.size() > 1 && nums[nums.size() - 1] == nums[0]) nums.pop_back();
        if (nums[nums.size() - 1] >= nums[0]) return nums[0];
        int l = 0, r = nums.size() - 1;
        while (l < r) {
            int mid = l + r >> 1;
            if (nums[mid] < nums[0]) r = mid;
            else l = mid + 1;
        }
        return nums[r];
    }
};

LeetCode 155. 最小栈(中等)

【题目描述】

设计一个支持 pushpoptop 操作,并能在常数时间内检索到最小元素的栈。

实现 MinStack 类:

  • MinStack() 初始化堆栈对象。
  • void push(int val) 将元素 val 推入堆栈。
  • void pop() 删除堆栈顶部的元素。
  • int top() 获取堆栈顶部的元素。
  • int getMin() 获取堆栈中的最小元素。

【示例 1】

输入:
["MinStack","push","push","push","getMin","pop","top","getMin"]
[[],[-2],[0],[-3],[],[],[],[]]

输出:
[null,null,null,null,-3,null,0,-2]

解释:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin();   --> 返回 -3.
minStack.pop();
minStack.top();      --> 返回 0.
minStack.getMin();   --> 返回 -2.

【提示】

− 2 31 < = v a l < = 2 31 − 1 -2^{31} <= val <= 2^{31} - 1 231<=val<=2311
poptopgetMin 操作总是在非空栈上调用
pushpoptopgetMin最多被调用 3 ∗ 1 0 4 3 * 10^4 3104


【分析】

本题和单调栈没关系,我们要维护的是每个元素及其之前入栈的元素中的最小值,其实是 DP 的思想,即 f [ i ] f[i] f[i] 表示 1 ∼ i 1 \sim i 1i 的元素中的最小值,更新也很简单,每次添加一个新元素 x x x 就可以动态维护最小值:f[i] = min(f[i - 1], x)

可以进行一个小优化,减少一些常数级的空间开销,就是当新入栈的元素 a k a_k ak 大于 f f f 的栈顶元素时就不将 a k a_k ak 入栈到 f f f 中,否则就将 a k a_k ak 入栈到 f f f 中。出栈时如果 a k a_k ak 小于等于 f f f 的栈顶元素,说明之前 a k a_k ak 有入栈过 f f f,因此顺便在 f f f 中也将栈顶元素出栈。


【代码】

【普通版】

class MinStack {
private:
    stack<pair<int, int>> stk;  // first 为元素值,second 为当前元素以及之前入栈元素的最小值

public:
    MinStack() {}

    void push(int val) {
        if (stk.empty()) stk.push({val, val});
        else stk.push({val, min(stk.top().second, val)});
    }

    void pop() {
        stk.pop();
    }

    int top() {
        return stk.top().first;
    }

    int getMin() {
        return stk.top().second;
    }
};

/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack* obj = new MinStack();
 * obj->push(val);
 * obj->pop();
 * int param_3 = obj->top();
 * int param_4 = obj->getMin();
 */

【空间优化版】

class MinStack {
private:
    stack<int> stk, f;

public:
    MinStack() {}

    void push(int val) {
        if (f.empty() || val <= f.top()) f.push(val);
        stk.push(val);
    }

    void pop() {
        if (stk.top() <= f.top()) f.pop();
        stk.pop();
    }

    int top() {
        return stk.top();
    }

    int getMin() {
        return f.top();
    }
};

/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack* obj = new MinStack();
 * obj->push(val);
 * obj->pop();
 * int param_3 = obj->top();
 * int param_4 = obj->getMin();
 */

网站公告

今日签到

点亮在社区的每一天
去签到