代码随想录算法训练营第六十三天_第十章_单调栈 | 503.下一个更大元素Ⅱ、42. 接雨水

文章讲述了LeetCode的两道算法题,分别是寻找循环数组中每个元素的下一个更大元素和计算接雨水问题。对于第一个问题,提供了两种遍历和使用栈的方法;对于第二个问题,介绍了双指针、动态规划和单调栈三种解法,并分析了它们的时间复杂度和空间复杂度。
摘要由CSDN通过智能技术生成

LeetCode 503.下一个更大元素Ⅱ

        给定一个循环数组 nums ( nums[nums.length - 1] 的下一个元素是 nums[0] ),返回 nums 中每个元素的 下一个更大元素 。

        数字 x 的 下一个更大的元素 是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1 。

视频讲解文章讲解https://programmercarl.com/0503.%E4%B8%8B%E4%B8%80%E4%B8%AA%E6%9B%B4%E5%A4%A7%E5%85%83%E7%B4%A0II.html

  • 思路:
    • 代码随想录:遍历两遍nums
      • i 从 0 到 nums.size() * 2 - 1
      • i % nums.size() 从 0 到 nums.size() - 1,再从 0 到 nums.size() - 1
    • 自己:先遍历一遍,再处理栈中剩余下标
      • i 从 0 到 nums.size() - 1,栈中剩余下标对应元素右侧没有比它大的
      • 循环数组👉还需要到左侧去找
      • 由于左侧先入栈,右侧后入栈👉只要从 0 遍历到 st.top() - 1 就行,如果这个范围内找不到,说明是 nums中的最大值
  • 代码:
// 代码随想录:
class Solution {
public:
    vector<int> nextGreaterElements(vector<int>& nums) {
        vector<int> result(nums.size(), -1);
        if (nums.size() == 0) return result;
        stack<int> st;
        for (int i = 0; i < nums.size() * 2; ++i) {
            // 模拟遍历两遍nums,注意一下都是用i % nums.size()来操作
            while (!st.empty() && nums[i % nums.size()] > nums[st.top()]) {
                result[st.top()] = nums[i % nums.size()];
                st.pop();
            }
            st.push(i % nums.size());
        }
        return result;
    }
};
// 自己:
class Solution {
public:
    vector<int> nextGreaterElements(vector<int>& nums) {
        vector<int> result(nums.size(), -1);
        stack<int> st; // 单调栈:存放下标,栈底↘栈顶
        for (int i = 0; i < nums.size(); ++i) {
            while (!st.empty() && nums[st.top()] < nums[i]) {
                result[st.top()] = nums[i];
                st.pop();
            }
            st.push(i);
        }
        // 处理栈中剩余下标
        int j = 0;
        while (!st.empty() && j < st.top()) {
            if (nums[st.top()] < nums[j]) {
                result[st.top()] = nums[j];
                st.pop();
            } else {
                ++j;
            }
        }
        return result;
    }
};

注:其实栈中剩一个元素时就不必再找了,一定对应 nums的最大值,result 中值为 -1

LeetCode 42. 接雨水

        给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

注:相当于求蓝色部分面积 

文章讲解https://programmercarl.com/0042.%E6%8E%A5%E9%9B%A8%E6%B0%B4.html

  • 思路:
    • 双指针解法:按照列来计算雨水宽度为 1)👉每列雨水高度取决于该列左侧最高的柱子右侧最高的柱子中矮的那个柱子的高度。
      height[3] < height[7],故列4雨水高度取决于height[3]
      • 列 i 对应的雨水面积min(lHeight, rHeight) - height[i]
      • 第一个柱子和最后一个柱子不接雨水
        for (int i = 0; i < height.size(); i++) {
            // 第一个柱子和最后一个柱子不接雨水
            if (i == 0 || i == height.size() - 1) continue;
        }
      • 对于每一个 i,求 lHeight 和 rHeight(均大于 height[i] 时,列 i 才能储水)
        int rHeight = height[i]; // 记录右边柱子的最高高度
        int lHeight = height[i]; // 记录左边柱子的最高高度
        for (int r = i + 1; r < height.size(); r++) {
            if (height[r] > rHeight) rHeight = height[r];
        }
        for (int l = i - 1; l >= 0; l--) {
            if (height[l] > lHeight) lHeight = height[l];
        }
      • 计算列 i 的雨水面积:(注:h不会 < 0,但可能 == 0👉列 i 无法储水)
        int h = min(lHeight, rHeight) - height[i];
        if (h > 0) sum += h; // 注意只有h大于零的时候,才统计到总和中
    • 动态规划解法:双指针解法每到一个柱子 i 都向两边遍历一遍找 lHeight 和 rHeight,重复计算👉分别用两个dp数组记录列 i 对应的 lHeight rHeight
      • 数组 maxLeft:maxLeft[i] = max(height[i], maxLeft[i - 1]);(从左往右遍历)
      • 数组 maxRight:maxRight[i] = max(height[i], maxRight[i + 1]);(从右往左遍历)
      • 初始化:maxLeft[0] 和 maxRight[size - 1]
    • 单调栈解法按照行来计算雨水

    单调栈(存放下标):从栈顶到栈底(高度)递增;height[st.top()] < height[i]👉出现凹槽

    栈头第二个元素👉凹槽左边的柱子
    栈头元素👉凹槽底部的柱子
    当前遍历的 i👉凹槽右边的柱子

            遇到相同高度的柱子:更新栈内下标(旧下标出栈,新下标入栈)求宽度时,如果遇到相同高度的柱子,需要使用最右边的柱子来计算宽度相当于为保证栈内严格单调递增,将相同高度的柱子在宽度上进行折叠,只保留最右边的。

            通过 长 * 宽 计算雨水面积:柱子高度👉长,下标👉

  • 单调栈处理逻辑:

    序号表示实际计算顺序,每出现一个凹槽就计算一个

    先将下标0的柱子加入到栈中:

    st.push(0);

    从下标1开始遍历所有的柱子:

    for (int i = 1; i < height.size(); i++)

    如果当前遍历的柱子低于栈顶的柱子,入栈:

    if (height[i] < height[st.top()])  st.push(i);
    如果当前遍历的柱子等于栈顶的柱子,出栈、入栈:
    if (height[i] == height[st.top()]) { // 例如 5 5 1 7 这种情况
      st.pop();
      st.push(i);
    }
    ※如果当前遍历的柱子高于栈顶的柱子👉出现凹槽:
    // 先取栈顶元素作为底部,下标为 mid
    // 出栈,再取栈顶元素作为凹槽左边
    int h = min(height[st.top()], height[i]) - height[mid];
    凹槽的高度👆,凹槽 (st.top(), i) 的宽度👇
    int w = i - st.top() - 1 ;

    凹槽能接到的雨水:

    h * w

    出现凹槽的整体处理逻辑

    while (!st.empty() && height[i] > height[st.top()]) { // 注意这里是while,持续更新栈顶元素
        int mid = st.top();
        st.pop();
        // 栈不为空,才有左边的柱子,才能接水
        if (!st.empty()) {
            int h = min(height[st.top()], height[i]) - height[mid];
            int w = i - st.top() - 1; // 注意减一,只求中间宽度
            sum += h * w;
        }
        // 栈为空,不接水
    }
  • 代码:
// 双指针:
class Solution {
public:
    int trap(vector<int>& height) {
        int sum = 0;
        for (int i = 0; i < height.size(); i++) {
            // 第一个柱子和最后一个柱子不接雨水
            if (i == 0 || i == height.size() - 1) continue;

            int rHeight = height[i]; // 记录右边柱子的最高高度
            int lHeight = height[i]; // 记录左边柱子的最高高度
            for (int r = i + 1; r < height.size(); r++) {
                if (height[r] > rHeight) rHeight = height[r];
            }
            for (int l = i - 1; l >= 0; l--) {
                if (height[l] > lHeight) lHeight = height[l];
            }
            int h = min(lHeight, rHeight) - height[i];
            if (h > 0) sum += h;
        }
        return sum;
    }
};
// 时间复杂度:O(n^2)
// 空间复杂度:O(1)
// 动态规划:
class Solution {
public:
    int trap(vector<int>& height) {
        if (height.size() <= 2) return 0;
        vector<int> maxLeft(height.size(), 0);
        vector<int> maxRight(height.size(), 0);
        int size = maxRight.size();

        // 记录每个柱子左边柱子最大高度
        maxLeft[0] = height[0];
        for (int i = 1; i < size; i++) {
            maxLeft[i] = max(height[i], maxLeft[i - 1]);
        }
        // 记录每个柱子右边柱子最大高度
        maxRight[size - 1] = height[size - 1];
        for (int i = size - 2; i >= 0; i--) {
            maxRight[i] = max(height[i], maxRight[i + 1]);
        }
        // 求和
        int sum = 0;
        for (int i = 0; i < size; i++) {
            int count = min(maxLeft[i], maxRight[i]) - height[i];
            if (count > 0) sum += count;
        }
        return sum;
    }
};
// 单调栈:
// 版本一:从栈顶到栈底严格单调递增
class Solution {
public:
    int trap(vector<int>& height) {
        if (height.size() <= 2) return 0; // 接不了
        stack<int> st; // 存着下标,计算的时候用下标对应的柱子高度
        st.push(0);
        int sum = 0;
        for (int i = 1; i < height.size(); i++) {
            if (height[i] < height[st.top()]) {     // 情况一
                st.push(i);
            } if (height[i] == height[st.top()]) {  // 情况二
                st.pop(); // 其实这一句可以不加,效果是一样的,但处理相同的情况的思路却变了。
                st.push(i);
            } else {                                // 情况三
                while (!st.empty() && height[i] > height[st.top()]) { // 注意这里是while
                    int mid = st.top();
                    st.pop();
                    if (!st.empty()) {
                        int h = min(height[st.top()], height[i]) - height[mid];
                        int w = i - st.top() - 1; // 注意减一,只求中间宽度
                        sum += h * w;
                    }
                }
                st.push(i);
            }
        }
        return sum;
    }
};

// 精简版:合并情况一、二,从栈顶到栈底非严格单调递增(栈中可以有高度相同的柱子)
class Solution {
public:
    int trap(vector<int>& height) {
        stack<int> st;
        st.push(0);
        int sum = 0;
        for (int i = 1; i < height.size(); i++) {
            while (!st.empty() && height[i] > height[st.top()]) {
                int mid = st.top();
                st.pop();
                if (!st.empty()) {
                    int h = min(height[st.top()], height[i]) - height[mid];
                    int w = i - st.top() - 1;
                    sum += h * w;
                }
            }
            st.push(i); // 遇到 height[i] == height[st.top()] 没有出栈
        }
        return sum;
    }
};

以上图示例说明处理情况二时 出栈 / 不出栈区别

总结

        情形二不出栈,则可将情形一二合并,与情形三共用st.push(i),单调栈中不再是严格单调递增,可能出现凹槽高度为0的情形,相当于以连续等高柱子的最左一根计入结果

        无论以哪根等高柱与凹槽左边和右边进行计算,所得高度都一样,而宽度又只取决于左右的下标,故不论情形二出栈与否,情形三的代码都不用改,只是具体实现过程有所区别(如图)。

第二十二算法训练营主要涵盖了Leetcode题目中的三道题目,分别是Leetcode 28 "Find the Index of the First Occurrence in a String",Leetcode 977 "有序数组的平方",和Leetcode 209 "长度最小的子数组"。 首先是Leetcode 28题,题目要求在给定的字符串中找到第一个出现的字符的索引。思路是使用双指针来遍历字符串,一个指向字符串的开头,另一个指向字符串的结尾。通过比较两个指针所指向的字符是否相等来判断是否找到了第一个出现的字符。具体实现的代码如下: ```python def findIndex(self, s: str) -> int: left = 0 right = len(s) - 1 while left <= right: if s[left == s[right]: return left left += 1 right -= 1 return -1 ``` 下来是Leetcode 977题,题目要求对给定的有序数组中的元素进行平方,并按照非递减的顺序返回结果。这里由于数组已经是有序的,所以可以使用双指针的方法来解决问题。一个指针指向数组的开头,另一个指针指向数组的末尾。通过比较两个指针所指向的元素的绝对值的大小来确定哪个元素的平方应该放在结果数组的末尾。具体实现的代码如下: ```python def sortedSquares(self, nums: List[int]) -> List[int]: left = 0 right = len(nums) - 1 ans = [] while left <= right: if abs(nums[left]) >= abs(nums[right]): ans.append(nums[left ** 2) left += 1 else: ans.append(nums[right ** 2) right -= 1 return ans[::-1] ``` 最后是Leetcode 209题,题目要求在给定的数组中找到长度最小的子数组,
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值