今日内容
每日温度
496.下一个更大元素 I
503.下一个更大元素II
接雨水
739. 每日温度※
题目链接: https://leetcode.cn/problems/daily-temperatures/ 个人博客: https://www.yuanql.top/2023/07/23/02_leetcode/739.%20%E6%AF%8F%E6%97%A5%E6%B8%A9%E5%BA%A6/ 代码随想录: https://programmercarl.com/0739.%E6%AF%8F%E6%97%A5%E6%B8%A9%E5%BA%A6.html
496.下一个更大元素 I※
题目链接: https://leetcode.cn/problems/next-greater-element-i/ 个人博客: https://www.yuanql.top/2023/07/23/02_leetcode/496.%20%E4%B8%8B%E4%B8%80%E4%B8%AA%E6%9B%B4%E5%A4%A7%E5%85%83%E7%B4%A0%20I/ 代码随想录: https://programmercarl.com/0496.%E4%B8%8B%E4%B8%80%E4%B8%AA%E6%9B%B4%E5%A4%A7%E5%85%83%E7%B4%A0I.html
503.下一个更大元素II※
题目链接: https://leetcode.cn/problems/next-greater-element-ii/ 个人博客: https://www.yuanql.top/2023/07/23/02_leetcode/503.%20%E4%B8%8B%E4%B8%80%E4%B8%AA%E6%9B%B4%E5%A4%A7%E5%85%83%E7%B4%A0%20II/ 代码随想录: 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
42. 接雨水※
题目链接: https://leetcode.cn/problems/trapping-rain-water/ 个人博客: https://www.yuanql.top/2023/07/23/02_02_leetcode_%E6%AF%8F%E6%97%A5%E4%B8%80%E9%A2%98/2023%E5%B9%B47%E6%9C%8823%E6%97%A5%E6%AF%8F%E6%97%A5%E4%B8%80%E9%A2%98--42.%20%E6%8E%A5%E9%9B%A8%E6%B0%B4/ 代码随想录: https://programmercarl.com/0042.%E6%8E%A5%E9%9B%A8%E6%B0%B4.html
84.柱状图中最大的矩形※ 建议 :
题目链接: https://leetcode.cn/problems/largest-rectangle-in-histogram/ 文章讲解: https://programmercarl.com/0084.%E6%9F%B1%E7%8A%B6%E5%9B%BE%E4%B8%AD%E6%9C%80%E5%A4%A7%E7%9A%84%E7%9F%A9%E5%BD%A2.html 视频讲解:
题目分析
方案一 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Solution { public int largestRectangleArea (int [] heights) { Deque<Integer> deque = new ArrayDeque <>(); int result = 0 ; int [] newHeights = new int [heights.length + 2 ]; newHeights[0 ] = 0 ; newHeights[newHeights.length - 1 ] = 0 ; for (int index = 0 ; index < heights.length; index++){ newHeights[index + 1 ] = heights[index]; } for (int i = 0 ; i < newHeights.length; i++) { while (!deque.isEmpty() && newHeights[deque.peek()] > newHeights[i]) { int poll = deque.poll(); result = Math.max(result, (i - deque.peek() - 1 ) * newHeights[poll]); } deque.push(i); } return result; } }
结果 解答成功: 执行耗时:18 ms,击败了90.16% 的Java用户 内存消耗:55.2 MB,击败了52.82% 的Java用户
分析 时间复杂度: O( n )
空间复杂度: O( n )
代码随想录 https://programmercarl.com/0084.%E6%9F%B1%E7%8A%B6%E5%9B%BE%E4%B8%AD%E6%9C%80%E5%A4%A7%E7%9A%84%E7%9F%A9%E5%BD%A2.html
思路 本题和 42. 接雨水 ,是遥相呼应的两道题目,建议都要仔细做一做,原理上有很多相同的地方,但细节上又有差异,更可以加深对单调栈的理解!
其实这两道题目先做那一道都可以,但我先写的42.接雨水的题解,所以如果没做过接雨水的话,建议先做一做接雨水,可以参考我的题解: 42. 接雨水
我们先来看一下暴力解法的解法:
暴力解法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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, w * h); } return sum; } };
如上代码并不能通过leetcode,超时了,因为时间复杂度是$O(n^2)$。
双指针解法 本题双指针的写法整体思路和 42. 接雨水 是一致的,但要比 42. 接雨水 难一些。
难就难在本题要记录记录每个柱子 左边第一个小于该柱子的下标,而不是左边第一个小于该柱子的高度。
所以需要循环查找,也就是下面在寻找的过程中使用了while,详细请看下面注释,整理思路在题解: 42. 接雨水 中已经介绍了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class Solution {public : int largestRectangleArea (vector<int >& heights) { vector<int > minLeftIndex (heights.size()) ; vector<int > minRightIndex (heights.size()) ; int size = heights.size (); minLeftIndex[0 ] = -1 ; for (int i = 1 ; i < size; i++) { int t = i - 1 ; 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 ; while (t < size && heights[t] >= heights[i]) t = minRightIndex[t]; minRightIndex[i] = t; } int result = 0 ; for (int i = 0 ; i < size; i++) { int sum = heights[i] * (minRightIndex[i] - minLeftIndex[i] - 1 ); result = max (sum, result); } return result; } };
单调栈 本地单调栈的解法和接雨水的题目是遥相呼应的。
为什么这么说呢, 42. 接雨水 是找每个柱子左右两边第一个大于该柱子高度的柱子,而本题是找每个柱子左右两边第一个小于该柱子的柱子。
这里就涉及到了单调栈很重要的性质,就是单调栈里的顺序,是从小到大还是从大到小 。
在题解 42. 接雨水 中我讲解了接雨水的单调栈从栈头(元素从栈头弹出)到栈底的顺序应该是从小到大的顺序。
那么因为本题是要找每个柱子左右两边第一个小于该柱子的柱子,所以从栈头(元素从栈头弹出)到栈底的顺序应该是从大到小的顺序!
我来举一个例子,如图:
只有栈里从大到小的顺序,才能保证栈顶元素找到左右两边第一个小于栈顶元素的柱子。
所以本题单调栈的顺序正好与接雨水反过来。
此时大家应该可以发现其实就是栈顶和栈顶的下一个元素以及要入栈的三个元素组成了我们要求最大面积的高度和宽度
理解这一点,对单调栈就掌握的比较到位了。
除了栈内元素顺序和接雨水不同,剩下的逻辑就都差不多了,在题解 42. 接雨水 我已经对单调栈的各个方面做了详细讲解,这里就不赘述了。
主要就是分析清楚如下三种情况:
情况一:当前遍历的元素heights[i]大于栈顶元素heights[st.top()]的情况
情况二:当前遍历的元素heights[i]等于栈顶元素heights[st.top()]的情况
情况三:当前遍历的元素heights[i]小于栈顶元素heights[st.top()]的情况
C++代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 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++) { if (heights[i] > heights[st.top ()]) { st.push (i); } else if (heights[i] == heights[st.top ()]) { st.pop (); 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 ; int h = heights[mid]; result = max (result, w * h); } } st.push (i); } } return result; } };
首先来说末尾为什么要加元素0?
如果数组本身就是升序的,例如[2,4,6,8],那么入栈之后 都是单调递减,一直都没有走 情况三 计算结果的哪一步,所以最后输出的就是0了。 如图:
那么结尾加一个0,就会让栈里的所有元素,走到情况三的逻辑。
开头为什么要加元素0?
如果数组本身是降序的,例如 [8,6,4,2],在 8 入栈后,6 开始与8 进行比较,此时我们得到 mid(8),rigt(6),但是得不到 left。
(mid、left,right 都是对应版本一里的逻辑)
因为 将 8 弹出之后,栈里没有元素了,那么为了避免空栈取值,直接跳过了计算结果的逻辑。
之后又将6 加入栈(此时8已经弹出了),然后 就是 4 与 栈口元素 8 进行比较,周而复始,那么计算的最后结果resutl就是0。 如图所示:
所以我们需要在 height数组前后各加一个元素0。
版本一代码精简之后:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution {public : int largestRectangleArea (vector<int >& heights) { stack<int > st; heights.insert (heights.begin (), 0 ); heights.push_back (0 ); st.push (0 ); int result = 0 ; for (int i = 1 ; i < heights.size (); i++) { while (heights[i] < heights[st.top ()]) { int mid = st.top (); st.pop (); int w = i - st.top () - 1 ; int h = heights[mid]; result = max (result, w * h); } st.push (i); } return result; } };
代码实现 暴力解法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Solution { public int largestRectangleArea (int [] heights) { int length = heights.length; int [] minLeftIndex = new int [length]; int [] minRightIndex = new int [length]; minLeftIndex[0 ] = -1 ; for (int i = 1 ; i < length; i++) { int t = i - 1 ; while (t >= 0 && heights[t] >= heights[i]) t = minLeftIndex[t]; minLeftIndex[i] = t; } minRightIndex[length - 1 ] = length; for (int i = length - 2 ; i >= 0 ; i--) { int t = i + 1 ; while (t < length && heights[t] >= heights[i]) t = minRightIndex[t]; minRightIndex[i] = t; } int result = 0 ; for (int i = 0 ; i < length; i++) { int sum = heights[i] * (minRightIndex[i] - minLeftIndex[i] - 1 ); result = Math.max(sum, result); } return result; } }
单调栈:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 class Solution { int largestRectangleArea (int [] heights) { Stack<Integer> st = new Stack <Integer>(); int [] newHeights = new int [heights.length + 2 ]; newHeights[0 ] = 0 ; newHeights[newHeights.length - 1 ] = 0 ; for (int index = 0 ; index < heights.length; index++){ newHeights[index + 1 ] = heights[index]; } heights = newHeights; st.push(0 ); int result = 0 ; for (int i = 1 ; i < heights.length; i++) { if (heights[i] > heights[st.peek()]) { st.push(i); } else if (heights[i] == heights[st.peek()]) { st.pop(); st.push(i); } else { while (heights[i] < heights[st.peek()]) { int mid = st.peek(); st.pop(); int left = st.peek(); int right = i; int w = right - left - 1 ; int h = heights[mid]; result = Math.max(result, w * h); } st.push(i); } } return result; } }
单调栈精简
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Solution { public int largestRectangleArea (int [] heights) { int [] newHeight = new int [heights.length + 2 ]; System.arraycopy(heights, 0 , newHeight, 1 , heights.length); newHeight[heights.length+1 ] = 0 ; newHeight[0 ] = 0 ; Stack<Integer> stack = new Stack <>(); stack.push(0 ); int res = 0 ; for (int i = 1 ; i < newHeight.length; i++) { while (newHeight[i] < newHeight[stack.peek()]) { int mid = stack.pop(); int w = i - stack.peek() - 1 ; int h = newHeight[mid]; res = Math.max(res, w * h); } stack.push(i); } return res; } }