文章目录
- 1.503下一个更大元素II
- 1.1.题目
- 1.2.解答
- 2.42接雨水
- 2.1.题目
- 2.2.解答
- 2.2.1.双指针for循环解法
- 2.2.3.单调栈解法
1.503下一个更大元素II
参考:代码随想录,503下一个更大元素II;力扣题目链接
1.1.题目
1.2.解答
做本题之前建议先做 739. 每日温度 (opens new window) 和 496.下一个更大元素 I。
这道题和739. 每日温度也几乎如出一辙,不同的是本题要循环数组了。
相信不少同学看到这道题,就想那我直接把两个数组拼接在一起,然后使用单调栈求下一个最大值不就行了!这样思路确实是对的。
注意:为什么说把两个数组拼到一起结果就是求了一个循环的数组。因为假设最后一个数字,在它前面有比他大的数字,但是如果不是循环数组,那么由于它是最后一个数字,他右边就没有比他大的数字了。但是现在如果再把数组在后面拼接一次,就相当于最后一个数字又从开始往后去寻找第一个比他大的数字了,因此这样就形成了一个循环的效果。
将两个nums数组拼接在一起,使用单调栈计算出每一个元素的下一个最大值,最后再把结果集即result数组resize到原数组大小就可以了。
代码如下:
// 版本一
class Solution {
public:
vector<int> nextGreaterElements(vector<int>& nums) {
// 拼接一个新的nums
vector<int> nums1(nums.begin(), nums.end());
nums.insert(nums.end(), nums1.begin(), nums1.end());
// 用新的nums大小来初始化result
vector<int> result(nums.size(), -1);
if (nums.size() == 0) return result;
// 开始单调栈
stack<int> st;
for (int i = 0; i < nums.size(); i++) {
while (!st.empty() && nums[i] > nums[st.top()]) {
result[st.top()] = nums[i];
st.pop();
}
st.push(i);
}
// 最后再把结果集即result数组resize到原数组大小
result.resize(nums.size() / 2);
return result;
}
};
这种写法确实比较直观,但做了很多无用操作,例如修改了nums数组,而且最后还要把result数组resize回去。resize倒是不费时间,是O(1)的操作,但扩充nums数组相当于多了一个O(n)的操作。
其实也可以不扩充nums,而是在遍历的过程中模拟走了两边nums。这种方式的代码如下,其中用i % nums.size()
来保证数组不越界,也就是重新从头开始访问数组中的元素。
vector<int> nextGreaterElements(vector<int> &nums)
{
vector<int> result(nums.size(), -1); // 定义最终结果并初始化成-1
if(nums.empty())
return result;
stack<int> st; // 单调栈
st.push(0); // 先把第一个数的索引加进去
// 开始数组:这里遍历两次,表示循环数组
for(int i = 1; i < 2*nums.size(); i++)
{
int cur = i % nums.size();
// 如果当前数字 < 栈顶数组,则满足单调栈要求,可以入栈
if(nums[cur] <= nums[st.top()])
{
st.push(cur);
}
else
{
while(!st.empty() && nums[cur] > nums[st.top()])
{
result[st.top()] = nums[cur]; // 记录结果
st.pop(); // 出栈
}
st.push(cur);
}
}
return result;
}
注意:编程中遇到栈越界的错误,因为自己写的过程中一直用到cur
的索引和st.top()
的值,所以就定义了两个变量把他们存起来了,其中cur
就是当前遍历的元素的索引,这个在一次for循环中保持不变,所以没什么问题。
// 开始数组:这里遍历两次,表示循环数组
for(int i = 1; i < 2*nums.size(); i++)
{
int cur = i % nums.size();
int top = st.top();
// 如果当前数字 < 栈顶数组,则满足单调栈要求,可以入栈
if(nums[cur] <= nums[top])
{
st.push(cur);
}
else
{
while(!st.empty() && nums[cur] > nums[top])
{
result[top] = nums[cur]; // 记录结果
st.pop(); // 出栈
// 注意:问题就出在这里,因为此时弹出之后栈可能为空了,导致内存越界错误
top = st.top(); // 更新栈顶的索引
}
st.push(cur);
}
}
但是top
的变量在while
循环中会st.pop()
,然后为了更新栈顶的元素值,会写top = st.top()
,此时就犯了栈溢出的错误,因为上次弹出之后,可能栈为空了,此时调用st.pop()
就会报如下的错误:
因此,对于栈的使用,最好还是一直坚持用st.top()
来访问,尤其是涉及到栈的弹出的时候。
2.42接雨水
参考:代码随想录,42接雨水;力扣题目链接
2.1.题目
2.2.解答
这部分有三种解法,分别是双指针for循环解法、动态规划解法和单调栈的解法。
具体的解法在代码随想录的网站上一节讲解的非常详细了,目前只看了双指针for循环解法和单调栈的解法,分别用的是列求解和行求解的方法,其实都不太难。
具体内容去看代码随想录吧,这里不再给出了,因为感觉并不是很难。
2.2.1.双指针for循环解法
实际测试此代码在力扣上会超时。
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 l_h = height[i];
int r_h = height[i];
// 寻找左侧的最大高度
for(int l = i - 1; l >= 0; l--)
if(height[l] > l_h)
l_h = height[l];
// 寻找右侧的最大高度
for(int r = i + 1; r < height.size(); i++)
if(height[r] > r_h)
r_h = height[r];
// 计算这一列接的雨水,加到总和中(注意这里计算的雨水高度一定是>=0的,所以不用判断>0)
sum = sum + min(l_h, r_h) - height[i];
}
return sum;
}
2.2.3.单调栈解法
int trap(vector<int> &height)
{
int sum = 0; // 最终结果
if(height.empty())
return sum;
stack<int> st;
st.push(0); // 先存入第一个元素
// 遍历所有元素
for(int i = 1; i < height.size(); i++)
{
// 1.仍然保持下降:则无法构成凹槽,继续加入单调栈
if(height[i] < height[st.top()])
{
st.push(i);
}
// 2.持平:需要更新右边界,因为下面计算宽度w的时候需要用右边界计算宽度,
// 所以这里把原先的高度的索引弹出,然后加入当前的索引
else if(height[i] == height[st.top()])
{
st.pop();
st.push(i);
}
// 3.上升:则出现凹槽,对每一个凹槽按照行来计算
else
{
while(!st.empty() && height[i] > height[st.top()])
{
int mid = height[st.top()]; // mid就是凹槽的高度
st.pop();
// 注意这里弹出后要判断非空才继续算,比如最左边两个数是上升的,遍历到第2个数的时候
// 满足上数条件,即i=1, st.top()=0。但是这样明显是无法接雨水的,因为左边没有了
if(!st.empty())
{
// 计算高度,是左右边界的高度的最小值 - 中间凹槽的高度
int h = min(height[st.top()], height[i]) - mid;
// 计算宽度,注意-1,因为i是右边界,st.top()是左边界,只有中间才是宽度
int w = i - st.top() - 1;
sum += h * w;
}
}
st.push(i);
}
}
return sum;
}