文章目录
42 接雨水
题目描述
方法一 暴力法
对于数组中的每个元素(柱子,包括高度为0的柱子),找出其下雨后所能达到的最高位置,该柱子的积水量就是两边最大高度的较小值减去当前柱子的高度。
比如说:[0,1,0,2,1,0,1,3,2,1,2,1]
下标为1的柱子高度为1,其左边(包括其本身)最大高度为1,右边(包括其本身)最大高度为3,那么所能达到的高度为1,积水量为1-1=0;
下标为2的柱子高度为0,其左边(包括其本身)最大高度为1,右边(包括其本身)最大高度为3,那么所能达到的高度为1,积水量为1-0=1。
该方法的时间复杂度为O(n²),空间复杂度为O(1)
class Solution {
public int trap(int[] height) {
int ans = 0;
int len = height.length;
for(int i=1; i<len-1; i++){
int maxLeft = 0, maxRight = 0;
for(int j=i; j>=0; j--){
maxLeft = Math.max(maxLeft, height[j]);
}
for(int j=i; j<len; j++){
maxRight = Math.max(maxRight, height[j]);
}
ans += Math.min(maxLeft, maxRight) - height[i];
}
return ans;
}
}
方法二 暴力法升级——动态编程
方法一中的暴力法是在遍历一个柱子时,通过内存的循环找出该柱子左右两边的最高柱子。方法二的改进在于,先找出每一根柱子的左右两边最高值,并用数组保存下来,这样就减少了一个内存循环,时间复杂度为O(n),因为使用了额外的数组空间,故空间复杂度也为O(n)。
class Solution {
public int trap(int[] height) {
if(height == null || height.length == 0)
return 0;
int ans = 0;
int len = height.length;
int[] maxLeft = new int[len];
int[] maxRight = new int[len];
//找出每根柱子左边的最高柱子
maxLeft[0] = height[0];
for(int i=1; i<len-1; i++)
maxLeft[i] = Math.max(height[i], maxLeft[i-1]);
//找出每根柱子右边的最高柱子
maxRight[len-1] = height[len-1];
for(int i=len-2; i>=0; i--)
maxRight[i] = Math.max(height[i], maxRight[i+1]);
//计算接到雨水的总量
for(int i=1; i<len-1; i++){
ans += Math.min(maxLeft[i], maxRight[i]) - height[i];
}
return ans;
}
}
方法三 单调递减栈
用栈来跟踪可能储水的最长的条形快,遍历数组时维护一个单调递减栈(新添加进来的元素越来越小)。
如果当前条形块小于或等于栈顶的条形块,将该条形块入栈(方便起见,将其索引下标入栈,而不是条形块的高度值);
如果当前条形块大于栈顶的条形块,就可以确定栈顶的条形块被当前条形块和栈顶的前一个条形块所界定,然后弹出栈顶元素并且累加答案到ans。
举一个简单的例子:
柱子高度分别为[3,2,1,4]
因为3>2>1,所以栈里的数据是[3,2,1]
遍历到4时,4大于栈顶元素1,栈顶元素1就被2和4所界定,如下图
然后栈顶元素1出栈,此时栈顶元素2<4,那么栈顶元素2被3和4所界定。如下图
时间复杂度为O(n),空间复杂度为O(n)。
class Solution {
public int trap(int[] height) {
if(height == null || height.length == 0)
return 0;
int len = height.length;
int ans = 0;
Deque<Integer> stack = new ArrayDeque<>();
for(int i=0; i<len; i++){
while(!stack.isEmpty() && height[i] > height[stack.peekLast()]){
int top = stack.removeLast();
if(stack.isEmpty())
break;
int distance = i - stack.getLast() -1;
ans += (Math.min(height[i], height[stack.getLast()]) - height[top]) * distance;
}
stack.addLast(i);
}
return ans;
}
}
方法四 双指针
时间复杂度为O(n),空间复杂度为O(1)。
class Solution {
public int trap(int[] height) {
int len = height.length;
int left = 0;
int right = len - 1;
int maxLeft = 0;
int maxRight = 0;
int ans = 0;
while(left < right){
if(height[left] < height[right]){
if(height[left] > maxLeft)
maxLeft = height[left];
else
ans += maxLeft - height[left];
left++;
}
else{
if(height[right] > maxRight)
maxRight = height[right];
else
ans += maxRight - height[right];
right--;
}
}
return ans;
}
}
84 柱状图中最大的矩形
题目描述
方法一 暴力法
枚举以每个柱形为高度的最大矩形的面积。依次遍历柱形的高度,然后对于每个高度往两边扩散,找出柱形的左右边界。
寻找左右边界直到当前柱形的高度小于选定高度为止。
时间复杂度为O(n2),空间复杂度为O(1)。
class Solution {
public int largestRectangleArea(int[] heights) {
int answer = 0;
for(int i=0; i<heights.length; i++){
int left = i;
while(left > 0 && heights[left-1] >= heights[i])
left--;
int right = i;
while(right < heights.length-1 && heights[right+1] >= heights[i])
right++;
int area = (right - left + 1) * heights[i];
answer = answer >= area ? answer : area;
}
return answer;
}
}
方法二 单调递增栈
方法一中的暴力法是通过内层循环找出刚好小于当前柱子高度的左右两边的柱子(边界)。而此方法通过维护一个单调栈即可得出当前柱子的左右两边边界。
维护一个单调栈,遍历数组,当数组元素大于栈顶元素,该数组元素入栈,保证栈中元素从栈底到栈顶递增,这样的话栈顶元素的上一个元素一直都是栈顶元素的左边界;
当数组元素小于或等于栈顶元素,当前数组元素就是栈顶元素的右边界,此时栈顶元素出栈,计算以栈顶元素为高度的矩形的面积。
tip: 这里用到了一个哨兵节点,就不用判断栈空的情况。比如说,没在栈底加哨兵节点的情况下,当栈中只有一个元素5,遍历到数组元素3,就要计算面积,将5出栈,并获取当前栈顶元素为5的左边界,但此时栈是空的,就会造成程序对一个空栈进行了peek操作。
同样程序也可以在结尾加一个哨兵,以省去处理后续元素一直递增的情况(程序中的最后一个while循环)。结尾哨兵的添加需要修改数组,将结尾哨兵节点放到数组的末尾。
class Solution {
public int largestRectangleArea(int[] heights) {
int len = heights.length;
if (len == 0) {
return 0;
}
if (len == 1) {
return heights[0];
}
int answer = 0;
Deque<Integer> stack = new ArrayDeque<>();
stack.addLast(-1);//哨兵节点
for (int i = 0; i < len; i++) {
while(stack.peekLast() != -1 && heights[stack.peekLast()] >= heights[i])
answer = Math.max(answer, heights[stack.pollLast()] * (i - stack.peekLast() -1));
stack.addLast(i);
}
//遍历完数组,最后一段一直是递增的情况
while(stack.peekLast() != -1){
answer = Math.max(answer, heights[stack.pollLast()] * (len - stack.peekLast() - 1));
}
return answer;
}
}
239 滑动窗口的最大值
题目描述
题目链接
给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回滑动窗口中的最大值。
进阶:
你能在线性时间复杂度内解决此题吗?
示例:
输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
输出: [3,3,5,5,6,7]
解释:
滑动窗口的位置 最大值
--------------- -----
[1 3 -1] -3 5 3 6 7 3
1 [3 -1 -3] 5 3 6 7 3
1 3 [-1 -3 5] 3 6 7 5
1 3 -1 [-3 5 3] 6 7 5
1 3 -1 -3 [5 3 6] 7 6
1 3 -1 -3 5 [3 6 7] 7
提示:
- 1 <= nums.length <= 10^5
- -10^4 <= nums[i] <= 10^4
- 1 <= k <= nums.length
单调队列
与单调栈类似,入队的都是比之前的元素小,类似于单调递减栈。在双端队列中仅维持k个数,大于k个数时将队列头出队,以保证双端队列的队列头元素是k个元素中最大的。
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
Deque<Integer> window = new ArrayDeque<>();
int len = nums.length;
int[] result = new int[len - k + 1];
if(len == 0 || k == 0)
return new int[0];
if(k == 1)
return nums;
for(int i=0; i<len; i++){
while(!window.isEmpty() && nums[window.getLast()] <= nums[i])
window.pollLast();
window.addLast(i);
//窗口里的数超过k个,队列首部出队
if(window.peekFirst() <= i - k)
window.pollFirst();
//队列满k个,开始记录最大值
if(i + 1 >= k)
result[i+1-k] = nums[window.getFirst()];
}
return result;
}
}
739.每日温度
题目描述
题目链接
请根据每日 气温 列表,重新生成一个列表。对应位置的输出为:要想观测到更高的气温,至少需要等待的天数。如果气温在这之后都不会升高,请在该位置用 0 来代替。
例如,给定一个列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]。
提示:气温 列表长度的范围是 [1, 30000]。每个气温的值的均为华氏度,都是在 [30, 100] 范围内的整数。
单调递减栈
维护一个单调递减栈,当当前元素小于栈顶元素时入栈;而当大于栈顶元素时,说明当前元素是栈顶元素之后的一个更高气温,计算差值存放到结果数组即可。
class Solution {
public int[] dailyTemperatures(int[] T) {
//维持一个单调递减栈
int len = T.length;
int[] ans = new int[len];
Deque<Integer> stack = new ArrayDeque<>();
for(int i = 0; i < len; i++){
while(!stack.isEmpty() && T[i] > T[stack.peekLast()]){
//当前元素大于栈顶元素,计算相差的天数
ans[stack.peekLast()] = i - stack.pollLast();
}
stack.addLast(i);
}
return ans;
}
}