Leetcode 503.下一个更大元素II
题目链接:Leetcode 503.下一个更大元素II
题目描述: 给定一个循环数组 nums
( nums[nums.length - 1]
的下一个元素是 nums[0]
),返回 nums
中每个元素的下一个更大元素 。数字 x
的 下一个更大的元素是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1
。
思路: 本题是循环数组,如果了解过约瑟夫问题的话很容易想到利用i % nums.size()
来代替单调栈模板的i
,接下来就很简单了。
代码如下:
class Solution {
public:
vector<int> nextGreaterElements(vector<int>& nums) {
int n = nums.size();
vector<int> result(n, -1);
stack<int> st;
st.push(0);
for (int i = 1; i < 2 * n; i++) { // 模拟遍历两边nums,因此是2*n
int j = i % n;
if (nums[j] <= nums[st.top()]) {
st.push(j);
} else {
while (!st.empty() && nums[j] > nums[st.top()]) {
result[st.top()] = nums[j];
st.pop();
}
st.push(j);
}
}
return result;
}
};
- 时间复杂度: O ( n ) O(n) O(n)
- 空间复杂度: O ( n ) O(n) O(n)
Leetcode 42. 接雨水
题目链接:Leetcode 42. 接雨水
题目描述: 给定 n
个非负整数表示每个宽度为 1
的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
思路: 本题很容易想到一种暴力思路:两层循环,找到每个位置的左右两侧的最大高度的最小值,但是很不幸这种方法超时了,因此需要想办法优化。回想起单调栈的应用范围:在一维数组中,寻找任一个元素的右边或者左边第一个比自己大或者小的元素的位置。 因此可以利用单调栈。
代码如下:(单调栈)
class Solution {
public:
int trap(vector<int>& height) {
int n = height.size();
if (n <= 2)
return 0;
stack<int> st;
st.push(0);
int sum = 0;
for (int i = 1; i < n; i++) {
// 只要当前元素不大于栈顶元素,直接加进栈
if (height[i] <= height[st.top()]) {
st.push(i);
} else {//否则需要进行处理
while (!st.empty() && height[i] > height[st.top()]) {
int mid = st.top(); // 保存最小的柱子
st.pop();
if (!st.empty()) {//不要少了这个判断
int h = min(height[i], height[st.top()]) - height[mid];
int w = i - st.top() - 1;
sum += w * h;
}
}
st.push(i);//处理之后,将当前元素加进栈
}
}
return sum;
}
};
- 时间复杂度: O ( n ) O(n) O(n)
- 空间复杂度: O ( n ) O(n) O(n)
我们发现暴力的思路时间复杂度高是因为对于每个下标位置都需要向两边扫描,那有没有一种方法只需要向两边扫描一次就可以得到所有元素两侧的最大高度呢?答案是动态规划。
代码如下:(dp)
class Solution {
public:
int trap(vector<int>& height) {
int n = height.size();
if (n == 0)
return 0;
// 利用dp维护两个数组,记录第i个柱子的左、右比它高的柱子
// 如果没有,则为柱子本身高度
vector<int> left(n);
left[0] = height[0]; // 初始化
for (int i = 1; i < n; i++) { // 从左向右遍历
left[i] = max(left[i - 1], height[i]);
}
vector<int> right(n);
right[n - 1] = height[n - 1]; // 初始化
for (int i = n - 2; i >= 0; i--) { // 从右向左遍历
right[i] = max(right[i + 1], height[i]);
}
int result = 0;
for (int i = 0; i < n; i++) {
result += min(left[i], right[i]) - height[i];
}
return result;
}
};
- 时间复杂度: O ( n ) O(n) O(n)
- 空间复杂度: O ( n ) O(n) O(n)
我们发现,下标 i
处能接的雨水量由 leftMax[i]
和 rightMax[i]
中的最小值决定。由于数组 leftMax
是从左往右计算,数组 rightMax
是从右往左计算,二者更新并不冲突,因此可以使用双指针和两个变量代替两个数组。(类似于动态规划的滚动数组优化)
两个指针遍历过程中,我们利用height[i]
和height[j]
来更新 leftMax
和rightMax
代码如下:(双指针)
class Solution {
public:
int trap(vector<int>& height) {
int result = 0;
int i = 0, j = height.size() - 1;//两个指针从两侧遍历
int leftMax = 0, rightMax = 0;//保存左右指针遍历过的最大值
while (i < j) {
leftMax = max(leftMax, height[i]);
rightMax = max(rightMax, height[j]);
if (height[i] < height[j]) {//此时leftMax一定大于rightMax
result += leftMax - height[i];
i++;
} else {
result += rightMax - height[j];
j--;
}
}
return result;
}
};
- 时间复杂度: O ( n ) O(n) O(n)
- 空间复杂度: O ( 1 ) O(1) O(1)
总结: 单调栈逐渐熟练,Leetcode 42. 接雨水这道题值得反复思考
最后,如果文章有错误,请在评论区或私信指出,让我们共同进步!