奔涌的LeetCoder(三):栈(2)
单调栈
栈是很简单的一种数据结构,先进后出的逻辑顺序,非常符合某些问题的求解特点。
单调栈实际上就是栈,只是利用了一些巧妙的逻辑,使得每次新元素入栈后,栈内的元素都保持有序(单调递增或单调递减)。实际上,单调栈的用途并不算广泛,只适合处理一种典型的问题,叫做 Next Greater Element
的问题。本文将讲解单调栈解决这类问题的实际问题。
首先,讲解 Next Greater Number
的原始问题:给你一个数组,返回一个等长的数组,对应索引存储着下一个更大元素,如果没有更大的元素,就存 -1。在此简单举个栗子:
给你一个数组 [2,1,2,4,3],你返回数组 [4,2,4,-1,-1]。
解释:第一个 2 后面比 2 大的数是 4; 1 后面比 1 大的数是 2;第二个 2 后面比 2 大的数是 4; 4 后面没有比 4 大的数,填 -1;3 后面没有比 3 大的数,填 -1。
上述这个例子的的暴力解法很好想到,就是对每个元素后面都进行扫描,找到第一个更大的元素就行了。但是暴力解法的时间复杂度是 O(N * N),空间复杂度为O(1)。因此,我们考虑,是否可以用空间来换取时间复杂度的降低?实际是有可行性的。
下面,来几盘正菜,边吃边自己体会吧,我就懒得讲了!
496. 下一个更大元素 I[简单]
说明:
给定两个 没有重复元素 的数组 nums1
和 nums2
,其中nums1
是 nums2
的子集。找到 nums1
中每个元素在 nums2
中的下一个比其大的值。
nums1
中数字 x 的下一个更大元素是指 x 在 nums2
中对应位置的右边的第一个比 x 大的元素。如果不存在,对应位置输出 -1 。
示例:
输入: nums1 = [4,1,2], nums2 = [1,3,4,2].
输出: [-1,3,-1]
解释:
对于num1中的数字4,你无法在第二个数组中找到下一个更大的数字,因此输出 -1。
对于num1中的数字1,第二个数组中数字1右边的下一个较大数字是 3。
对于num1中的数字2,第二个数组中没有下一个更大的数字,因此输出 -1。
解答:
解法:单调栈。
public class Solution {
public int[] nextGreaterElement(int[] nums1, int[] nums2) {
LinkedList < Integer > stack = new LinkedList < > ();
HashMap < Integer, Integer > map = new HashMap < > ();
int[] res = new int[nums1.length];
for (int i = 0; i < nums2.length; i++) {
while (!stack.isEmpty() && nums2[i] > nums2[stack.peek()])
map.put(nums2[stack.pop()], nums2[i]);
stack.push(i);
}
while (!stack.isEmpty())
map.put(nums2[stack.pop()], -1);
for (int i = 0; i < nums1.length; i++) {
res[i] = map.get(nums1[i]);
}
return res;
}
}
84.柱状图中最大的矩形[困难]
说明:
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。求在该柱状图中,能够勾勒出来的矩形的最大面积。
以上是柱状图的示例,其中每个柱子的宽度为 1,给定的高度为 [2,1,5,6,2,3]。
图中阴影部分为所能勾勒出的最大矩形面积,其面积为 10 个单位。
示例:
输入: [2,1,5,6,2,3]
输出: 10
解答:
解法一:暴力破解法。依次遍历柱形的高度,对于每一个高度分别向两边扩散,求出以当前高度为矩形的最大宽度多少,然后乘以当前高度就是中最大的面积值。找出所有高度中对应的最大的面积值即为问题解。但显然,时间复杂度O(N*N),空间复杂度O(1)。
class Solution {
public int largestRectangleArea(int[] heights) {
int maxarea = 0;
for(int i = 0; i < heights.length; i ++){
int left = i; int right = i;
while(left > 0 && heights[i] <= heights[left-1])
left--;
while(right < heights.length - 1 && heights[i] <= heights[right+1])
right++;
int area = (right - left + 1) * heights[i];
maxarea = area > maxarea ? area : maxarea;
}
return maxarea;
}
}
解法二:单调栈。此题我们求解最大面积值的关键在于针对某一高度遍历周围的高度求出比当前高度小的第一个值。另外,常见的单调栈求的是单向的第一个大(小)值,此题需要我们遍历左右两个方向,因此需要利用单调栈两次。空时复杂度为O(N)。
class Solution {
public int largestRectangleArea(int[] heights) {
int n = heights.length;
int[] left = new int[n];
Arrays.fill(left,-1);
int[] right = new int[n];
Arrays.fill(right,n);
//定义单调栈
LinkedList<Integer> stack = new LinkedList<>();
//从左向右遍历,寻找比当前值小的第一个位置下标
for (int i = 0; i < n; i++) {
while(! stack.isEmpty() && heights[i] < heights[stack.peek()]){
right[stack.peek()] = i;
stack.pop();
}
stack.push(i);
}
stack.clear();
//从右向左遍历,寻找比当前值小的第一个位置下标
for (int i = n - 1; i >= 0; i--) {
while(! stack.isEmpty() && heights[i] < heights[stack.peek()]){
left[stack.peek()] = i;
stack.pop();
}
stack.push(i);
}
//得出最大面积值
int maxarea = 0;
for (int i = 0; i < n; i ++) {
int area = (right[i] - left[i] - 1) * heights[i];
maxarea = maxarea > area ? maxarea : area;
}
return maxarea;
}
}
解法三:单调栈。解法二考虑双向的遍历,相当于遍历两次数组,那么我考虑是否可以只遍历一次就找到解呢?其实简单改写一下就行了。
class Solution {
public int largestRectangleArea(int[] heights) {
int n = heights.length;
int[] left = new int[n];
Arrays.fill(left,-1);
int[] right = new int[n];
Arrays.fill(right,n);
//定义单调栈
LinkedList<Integer> stack = new LinkedList<>();
//从左向右遍历,寻找比当前值小的第一个位置下标
for (int i = 0; i < n; i++) {
while(! stack.isEmpty() && heights[i] < heights[stack.peek()]){
right[stack.peek()] = i;
stack.pop();
}
//把解法二的left遍历换成这语句
if(! stack.isEmpty())
left[i] = stack.peek();
stack.push(i);
}
//得出最大面积值
int maxarea = 0;
for (int i = 0; i < n; i ++) {
int area = (right[i] - left[i] - 1) * heights[i];
maxarea = maxarea > area ? maxarea : area;
}
return maxarea;
}
}
42.接雨水[困难]
说明:
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
示例:
输入: [0,1,0,2,1,0,1,3,2,1,2,1]
输出: 6
解答:
解法一:暴力遍历。对于数组中的每个元素,我们找出下雨后水能达到的最高位置,等于两边最大高度的较小值减去当前高度的值。时间复杂度O(N*N)。
class Solution {
public int trap(int[] height) {
int n = height.length;
int res = 0;
for(int i = 1; i < n -1; i++){
int maxleft = 0; int maxright = 0;
for(int j = i; j >= 0; j--){
maxleft = Math.max(maxleft, height[j]);
}
for(int j = i; j < n; j++){
maxright = Math.max(maxright, height[j]);
}
res += Math.min(maxleft,maxright) - height[i];
}
return res;
}
}
解法二:单调栈。此题也需要我们找出左右两边的第一个较大值,然后比较左右两个较大值的较小值和当前值的大小来确定高度。所以可以考虑单调栈的形式求解。
class Solution {
public int trap(int[] height) {
int n = height.length;
int res = 0;
LinkedList<Integer> stack = new LinkedList<>();
for(int i = 0; i <= n-1; i ++){
while(! stack.isEmpty() && height[i] > height[stack.peek()]){
int top = stack.pop();
if(stack.isEmpty())
break;
int widths = i - stack.peek() - 1;
int heights = Math.min(height[i],height[stack.peek()]) - height[top];
res += widths * heights;
}
stack.push(i);
}
return res;
}
}
彩蛋赛普勒斯:总结而言,就是在面对需要我们找出离当前值最近(可左可右)的较大值或较小值的时候,不妨试试暴力解法之外的单调栈。
文章会在个人公众号同步更新,欢迎关注!!!