暴力解法,双层for循环,时间复杂度是O(n^2)。
什么时候用单调栈?
通常是一维数组,要找任一个元素的右边(左边)第一个比自己大(小)的元素的位置,此时优先考虑单调栈,时间复杂度为O(n)。
使用单调栈时,首先要明确如下几点:
1)单调栈里存放的元素是什么?
单调栈只需要存放元素的下标i,T[i]直接获取对应的元素。
2)单调栈里元素是递增还是递减?
本题使用递增顺序(从栈头到栈底的顺序)。当单调栈递增,元素i入栈时,才能保证 元素i 是 栈顶元素 在数组中右边第一个比自己大的元素,也就是说找到栈顶元素 右边第一个比自己大的元素是 元素i。
3)三种情况:
当前遍历的元素T[i]小于栈顶元素T[st.top()]的情况
当前遍历的元素T[i]等于栈顶元素T[st.top()]的情况
当前遍历的元素T[i]大于栈顶元素T[st.top()]的情况
class Solution {
public:
vector<int> dailyTemperatures(vector<int>& temperatures) {
//递增栈
stack<int> st;//存放对应元素的下标
vector<int> result(temperatures.size(), 0);
st.push(0);//初始化
for(int i=1; i<temperatures.size(); i++)
{
if(temperatures[i] < temperatures[st.top()]) st.push(i);//情况1 当前元素比栈顶元素小 当前元素入栈
else if(temperatures[i] == temperatures[st.top()]) st.push(i);//情况2 当前元素等于栈顶元素 当前元素入栈
else//情况3 当前元素比栈顶元素大 保存结果 栈顶出栈 当前元素入栈
{
while(!st.empty() && temperatures[i] > temperatures[st.top()])
{
result[st.top()] = i - st.top();
st.pop();
}
st.push(i);
}
}
return result;
}
};
简化
class Solution {
public:
vector<int> dailyTemperatures(vector<int>& temperatures) {
//递增栈 简化
stack<int> st;
vector<int> result(temperatures.size(), 0);
st.push(0);
for(int i=1; i<temperatures.size(); i++)
{
while(!st.empty() && temperatures[i] > temperatures[st.top()])
{
result[st.top()] = i - st.top();
st.pop();
}
st.push(i);
}
return result;
}
};
result数组初始化应该为多少?
题目提示,如果不存在下一个更大元素就输出 -1,所以result数组初始化为-1。
遍历nums2时需要判断nums2[i]在nums1中是否出现过,因为最后是根据nums1元素的下标来更新result数组。
题目提示nums1 和 nums2是不重复的数组,因此可以用map做映射,根据数值快速找到下标,还可以判断nums2[i]是否在nums1中出现过。使用集合来解决哈希问题时,优先使用unordered_set,它的查询和增删效率是最优的。
单调递增栈顺序,栈头到栈底元素顺序要从小到大。栈内元素递增才能找到右边第一个比自己大的元素。
三种情况:
C++实现
class Solution {
public:
vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
stack<int> st;
vector<int> result(nums1.size(), -1);//处理的是nums1的元素
if(nums1.size() == 0) return result;
//没有重复元素 map做映射 根据数值快速找到下标,判断nums2[i]是否在nums1中出现过。
unordered_map<int, int> umap;// key:下标元素,value:下标 为了快速查找元素
for(int i=0; i<nums1.size(); i++) umap[nums1[i]] = i;
st.push(0);
for(int i=1; i<nums2.size(); i++)
{
if(nums2[i] < nums2[st.top()]) st.push(i);
else if(nums2[i] == nums2[st.top()]) st.push(i);
else{
while(!st.empty() && nums2[i] > nums2[st.top()])
{
if(umap.count(nums2[st.top()]) > 0)//判断nums2[i]是否在nums1中出现过
{
int index = umap[nums2[st.top()]];
result[index] = nums2[i];//nums2[i]在nums1出现过,记录结果
}
st.pop();
}
st.push(i);
}
}
return result;
}
};
简化
class Solution {
public:
vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
//三种情况合并写
stack<int> st;
vector<int> result(nums1.size(), -1);
if(nums1.size() == 0) return result;
st.push(0);
//映射容器
unordered_map<int, int> umap;
for(int i=0; i<nums1.size(); i++) umap[nums1[i]] = i;
//遍历nums2
for(int i=1; i<nums2.size(); i++)
{
while(!st.empty() && nums2[i] > nums2[st.top()])
{
if(umap.count(nums2[st.top()]) > 0)
{
int index = umap[nums2[st.top()]];
result[index] = nums2[i];
}
st.pop();
}
st.push(i);
}
return result;
}
};
把nums数组首尾拼接组成新的nums来操作,最后resize再返回结果。
class Solution {
public:
vector<int> nextGreaterElements(vector<int>& nums) {
//拼接数组
vector<int> nums1(nums.begin(), nums.end());
nums.insert(nums.end(), nums1.begin(), nums1.end());
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 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(nums.size() / 2);
return result;
}
};
在遍历的过程中模拟走了两边nums,用i % nums.size()来代替i 操作
class Solution {
public:
vector<int> nextGreaterElements(vector<int>& nums) {
//遍历中模拟走了两边nums 用i % nums.size()来操作
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() * 2; i++)
{
if(nums[i % nums.size()] < nums[st.top()]) st.push(i % nums.size());
else if(nums[i % nums.size()] == nums[st.top()]) st.push(i % nums.size());
else
{
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) {
//简化 遍历中模拟走了两边nums 用i % nums.size()来操作
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() * 2; i++)
{
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;
}
};
按照列来计算,宽度w=1,每一列雨水的高度取决于该列左侧最高的柱子和 右侧最高的柱子 ,两者中较矮的柱子高度。
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 leftheight = height[i];// 记录左边柱子的最高高度
int rightheight = height[i];// 记录右边柱子的最高高度
for(int l=i-1; l>=0; l--)
{
if(height[l] > leftheight) leftheight = height[l];
}
for(int r=i+1; r<height.size(); r++)
{
if(height[r] > rightheight) rightheight = height[r];
}
//统计高度差 累加
int h = min(rightheight, leftheight) - height[i];
if(h > 0) sum += h;
}
return sum;
}
};
暴力解法中,通过列来计算每一列的雨水体积再累加求出雨水总体积,其中,当前列雨水面积 = 1 * (min(左边柱子的最高高度,记录右边柱子的最高高度) - 当前柱子高度)。双指针遍历时,每到一个柱子都向两边遍历一遍,有重复计算。
优化:把每一个位置的左边最高高度记录在一个数组上maxLeft,右边最高高度记录在一个数组上maxRight,避免了重复计算:
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循环从1开始遍历
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]);
//累计高度 左右柱子中较矮的柱子高度-当前柱子高度=雨水长度
//求的是面积 宽度=1 相当于统计长度
int sum = 0;
int h = 0;
for(int i=0; i<size; i++)
{
h = min(maxright[i], maxleft[i]) - height[i];
if(h>=0) sum += h;
}
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;
int mid = 0;//凹槽最低
int h = 0, w = 0;
for(int i=1; i<height.size(); i++)
{
if(height[i] < height[st.top()]) st.push(i);//情况一
else if(height[i] == height[st.top()])//情况二 保存较近的柱子高度
{
st.pop();
st.push(i);
}
else//情况三 出现凹槽
{
while(!st.empty() && height[i] > height[st.top()])
{
mid = st.top();//注意这是柱子下标
st.pop();//弹出凹槽底部
if(!st.empty())//如果还有元素 该栈顶元素才是凹槽左边高度
{
h = min(height[st.top()], height[i]) - height[mid];
w = i - st.top() - 1; // 注意减一,只求中间宽度
sum += h * w;
}
}
st.push(i);
}
}
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;
int mid = 0, h = 0, w = 0;
for(int i=1; i<height.size(); i++)
{
while(!st.empty() && height[i] > height[st.top()])
{
mid = st.top();//凹槽底部
st.pop();
if(!st.empty())
{
h = min(height[i], height[st.top()]) - height[mid];
w = i - st.top() - 1;//减1才是凹槽底部的宽度
sum += h * w;
}
}
st.push(i);
}
return sum;
}
};
时间复杂度是 O ( n 2 ) O(n^2) O(n2)
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
//暴力解法 双指针 找左右最高柱子
int sum = 0;
for(int i=0; i<heights.size(); i++)
{
int left = i;
int right = i;
for(; left>=0; left--)
{
if(heights[left] < heights[i]) break;
}
for(; right<heights.size(); right++)
{
if(heights[right] < heights[i]) break;
}
int w = right - left - 1;
int h = heights[i];
sum = max(sum, h*w);
}
return sum;
}
};
重点在于要记录每个柱子的左边第一个小于该柱子的下标,而不是高度。所以需要循环查找,使用while
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
//双指针 优化
//记录的是左右两边最矮柱子中离得最近的柱子的下标
int size = heights.size();
vector<int> minleftindex(size);
vector<int> minrightindex(size);
// 记录每个柱子 左边第一个小于该柱子的下标
minleftindex[0] = -1;//初始化,防止下面while死循环
for(int i=1; i<size; i++)
{
int t = i-1;
//找到离得最近的左边最矮柱子 不用if,而是不断向左寻找的过程
while(t>=0 && heights[t] >= heights[i]) t = minleftindex[t];//记录该柱子的下标
minleftindex[i] = t;//容器中更新下标
}
// 记录每个柱子 右边第一个小于该柱子的下标
minrightindex[size-1] = size;
for(int i=size-2; i>=0; i--)
{
int t = i+1;
//不用if,而是不断向左寻找的过程
while(t<size && heights[t] >= heights[i]) t = minrightindex[t];
minrightindex[i] = t;
}
int sum = 0;
int result = 0;
for(int i=0; i<size; i++)
{
sum = heights[i] * (minrightindex[i] - minleftindex[i] - 1);
result = max(sum, result);
}
return result;
}
};
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
//单调栈
int result = 0;
stack<int> st;
//原数组头尾加入数字0分别为了避免数组是降序 升序时无法去三个值组成面积
heights.insert(heights.begin(), 0);
heights.push_back(0);
st.push(0);
//从大到小入栈 才能找到凸形面积
for(int i=1; i<heights.size(); i++)//数组第一个元素已经入栈
{
if(heights[i] > heights[st.top()]) st.push(i);
else if(heights[i] == heights[st.top()]) st.push(i);
else
{
while(!st.empty() && heights[i] < heights[st.top()])
{
int mid = st.top();
st.pop();
if(!st.empty())
{
int left = st.top();
int right = i;
int w = right - left - 1;//只要最高柱子的宽度
result = max(result, w * heights[mid]);
}
}
st.push(i);
}
}
return result;
}
};
化简
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
//单调栈 简化
int result = 0;
stack<int> st;
heights.insert(heights.begin(), 0);
heights.push_back(0);
st.push(0);
for(int i=1; i<heights.size(); i++)
{
while(!st.empty() && heights[i] < heights[st.top()])
{
int mid = st.top();
st.pop();
if(!st.empty())
{
//int left = st.top();
//int right = i;
//right - left - 1
int w = i - st.top() - 1;
result = max(result, heights[mid] * w);
}
}
st.push(i);
}
return result;
}
};
1)单调栈里存放的元素是什么?
单调栈只需要存放元素的下标i,T[i]直接获取对应的元素。
2)单调栈里元素是递增还是递减?
如果求一个元素右边第一个更大元素,单调栈是递增的,如果求一个元素右边第一个更小元素,单调栈是递减的。
当单调栈递增,元素i入栈时,才能保证 元素i 是 栈顶元素 在数组中右边第一个比自己大的元素,也就是说找到栈顶元素 右边第一个比自己大的元素是 元素i。
3)三种情况:
当前遍历的元素T[i]小于栈顶元素T[st.top()]的情况
当前遍历的元素T[i]等于栈顶元素T[st.top()]的情况
当前遍历的元素T[i]大于栈顶元素T[st.top()]的情况