题目描述:
给定一个循环数组(最后一个元素的下一个元素是数组的第一个元素),输出每个元素的下一个更大元素。数字x的下一个更大元素是按数组遍历顺序,这个数字之后的第一个比它更大的数,循环搜索它的下一个更大的数。如果不存在,输出-1。
例1:
输入:[1, 2, 1]
输出:[2, -1 , 2]
解释:第一个1的下一个更大的数是2;数字2找不到下一个更大的数输出-1;第二个1的下一个最大的数需要循环搜索,结果也是2.
当我们需要在一个循环数组中找到每个元素的下一个更大元素时,可以使用单调栈的思想来解决这个问题。
假设我们有一个循环数组 nums,为了方便处理,我们可以将它复制一份拼接到原数组的末尾,从而得到一个长度为原数组两倍的新数组 nums。这样处理后,我们就可以把循环数组 nums 看成一个普通的数组,从而用单调栈来解决。
接下来就是单调栈的操作:
我们用一个栈来存储数组元素的下标。首先,我们将数组的第一个元素的下标 push 进栈中,即 st.push(0)。
然后,从数组的第二个元素开始遍历数组,对于数组的每个元素 nums[i],如果它小于或等于栈顶元素所对应的元素 nums[st.top()],则将它的下标 i push 进栈中,表示它的下一个更大元素还没有找到。
如果 nums[i] 大于栈顶元素所对应的元素 nums[st.top()],则说明栈顶元素的下一个更大元素就是 nums[i],我们可以将栈顶元素所对应的结果 result[st.top()] 赋值为 nums[i],表示它的下一个更大元素是 nums[i],然后将栈顶元素 pop 出栈,继续判断新的栈顶元素和当前元素的大小关系。
重复执行上述操作,直到遍历完整个数组。最后,我们得到的结果数组 result 中存储的就是每个元素的下一个更大元素了。由于我们把原数组复制拼接到末尾,所以最后需要将结果数组 result 调整为原数组的大小,即执行 result.resize(nums.size() / 2)。
时间复杂度为 O(n),因为每个元素只会入栈出栈一次。
class Solution {
public:
vector<int> nextGreaterElements(vector<int>& nums) {
// 拼接一个新的nums,使其成为长度为原数组两倍的数组
vector<int> nums1(nums.begin(), nums.end());
// 将nums1中的所有元素插入到nums的末尾,从而得到一个新的nums
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;
// 单调栈的初始化
st.push(0);
for (int i = 1; i < nums.size(); i++) {
// 如果当前元素比栈顶元素小或相等,直接入栈
if (nums[i] <= nums[st.top()]) st.push(i);
// 如果当前元素比栈顶元素大,那么栈中所有比当前元素小的元素的下一个更大元素就是当前元素
else {
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;
}
};
题目描述:
给定n个非负整数表示每个宽度为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.将每一列的雨水高度求出来即可。
计算该列取决于左侧最高柱子和右侧最高柱子中最矮小的减去自身。转换为公式如下:
计算公式:min(左侧最高,右侧最高)- 本身高度。
结合下图更好理解:
列4 左侧最高的柱子是列3,高度为2(以下用lHeight表示)。
列4 右侧最高的柱子是列7,高度为3(以下用rHeight表示)。
列4 柱子的高度为1(以下用height表示)
那么列4的雨水高度为 列3和列7的高度最小值减列4高度,即: min(lHeight, rHeight) - height。
列4的雨水高度求出来了,宽度为1,相乘就是列4的雨水体积了。
同样的操作,从头遍历一遍所有的列,然后求出每一列雨水的体积。相加就是总雨水的体积了。注:第一个柱子和最后一个柱子不接雨水
代码如下:
for(int i = 0 ; i < height.size() ; i++){
// 第一个柱子和最后一个柱子不接雨水、
if(i == 0 || i == height.size() - 1) continue;
}
在for循环中求左右两边最高柱子,代码如下:
int rHeight = height[i]; // 记录右边柱子的最高高度
int lHeight = height[i]; // 记录左边柱子的最高高度
for(int r = i+1 ; i 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;// 注意只有h大于零的时候,才统计到总和之中。
优化:
之前为了得到两边的最高高度,使用双指针来遍历,每到一个柱子都行两边遍历一遍。考虑把每一个位置的左边最高高度记录在一个数组上(maxLeft),右边最高高度记录在一个数组上(maxRight),即可避免重复。
当前位置,左边的最高高度是前一个位置的左边最高高度和本高度的最大值
从左向右遍历:maxLeft[i] = max(height[i],maxLeft[i-1])
从右向左遍历:maxRight[i] = max(height[i], maxRight[i+1])
class Solution {
public:
int trap(vector<int>& height) {
// 如果height的长度小于等于2,那么无法形成水坑,直接返回0
if (height.size() <= 2) return 0;
// 初始化一个数组,用来记录每个柱子左边柱子最大高度
vector<int> maxLeft(height.size(), 0);
// 初始化一个数组,用来记录每个柱子右边柱子最大高度
vector<int> maxRight(height.size(), 0);
// 获取maxRight数组的大小
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; // 返回总的水量
}
};
[代码随想录]