单调栈系列
其中nums[i] > nums[st.peek()]是我们的核心,代表找到咯;实际上我们都是维护队列或栈中有一个最大值,无论是单调递增也好,还是单调递减也好
- 具体可以看看如下几题,大家就明白了
739每日温度
最简单的就是暴力解法,记录nums[j]>nums[i]的天数是j-i
//暴力解法时间复杂度On² 空间ON
class Solution {
public int[] dailyTemperatures(int[] temperatures) {
int[]res = new int[temperatures.length];
for(int i = 0;i<temperatures.length-1;i++){
for(int j = i+1;j<temperatures.length;j++){
if(temperatures[j]>temperatures[i]){
res[i] = j-i;
break;
}
}
}
return res;
}
}
//使用单调栈降低到On
class Solution {
public int[] dailyTemperatures(int[] temperatures) {
int n = temperatures.length;
int[]res = new int[n];
//存放元素索引
Stack<Integer> s = new Stack<>();
for(int i = n-1;i>=0;i--){
while(!s.isEmpty() && temperatures[s.peek()]<=temperatures[i]){
//小数出栈
s.pop();
}
res[i] = s.isEmpty()? 0:(s.peek()-i);
s.push(i);
}
return res;
}
}
496.下一个更大的元素
-
这一题和温度有点像但又不完全像,这里有两个数组,其中我们要比较的是nums1的数在nums2中有没有下一个更大的数,实际上就是要操作Nums2;
- 这里我们需要借助HashMap集合来存储,因为没有重复元素,所以我们将Nums1遍历后,记录其下表与数值,这样根据遍历Nums2的时候,找到合适的数了我们就可以找到对应的Index;
- 比如说,我们遍历nums2, 将nums2的数压进去,我们将nums2[0]压进去,我们的for从1开始
- nums[i] <= nums[stack.peek()],那么我们就可以加它压入栈中,此时也就不满足题意
- 如果Nums2[i]>栈顶元素,说明此时右边的大一些,这个时候判断map.cntainsKey(stack.peek())是否在1中,如果在的话,取出其索引值,对该索引进行赋值即可。
class Solution {
public int[] nextGreaterElement(int[] nums1, int[] nums2) {
Stack<Integer> temp= new Stack<>();
int[]res = new int[nums1.length];
//进行初始化,后面只需要对符合的进行改变
Arrays.fill(res,-1);
HashMap<Integer,Integer>map = new HashMap<>();
for(int i =0;i<nums1.length;i++){
map.put(nums1[i],i);
}
temp.push(0);//将Nums1先压进去
for(int i =1;i<nums2.length;i++){
if(nums2[i]<=nums2[temp.peek()]){
temp.push(i);
}else{
//代表找到下一个更大的元素了
while(!temp.isEmpty() && nums2[temp.peek()]<nums2[i]){
if(map.containsKey(nums2[temp.peek()])){
Integer index = map.get(nums2[temp.peek()]);
res[index] = nums2[i];
}
temp.pop();
}
//将nums[i]压入栈中继续进行比较
temp.push(i);
}
}
return res;
}
}
503:下一个更大的元素II
//和温度不一样的在于这里我们是一个循环数组,当设计到循环的时候就要有点循环队列的意识,对长度取模
//索引我们是index = i % size
class solution{
public int[]nextGreaterElements(int[]nums){
//进行边界的判断
if(nums==null || nums.length<=1){
return new int[]{-1};
}
int size = nums.length;
int [] result = new int[size];
//初始化数组-1
Arrays.fill(res,-1);
Stack<Integer>st = new Stack<>();
for(int i = 0;i<2 * size;i++){
//只要栈不为空,或者此时nums[i]>nums[st.peek()]就可加入res
while(!st.isEmpty() && nums[i%size] > nums[st.peek()]){
result[st.peek()] = nums[i%size];
st.pop();
}
st.push(i%size);
}
return result;
}
}
//法二
class Solution {
public int[] nextGreaterElements(int[] nums) {
int n = nums.length;
int[]res = new int[n];
Stack<Integer>stack = new Stack<>();
for(int i = 2*n-1;i>=0;i--){
while(!stack.isEmpty() && stack.peek()<=nums[i%n]){
stack.pop();
}
res[i % n] = stack.isEmpty()? -1: stack.peek();
stack.push(nums[i%n]);
}
return res;
}
}
42.接雨水
//单调栈,构造一个单调下减的栈【5,3,1
//雨水面积主要是根据我们弹出的最小的值减去凹槽的值
//雨水的高度是h = Math.min(height[st.peek()],height[i])-height[st.pop()];
//雨水的宽度是:i-st.peek()-1
class Solution {
public int trap(int[] height) {
int n = height.length;
int result = 0;
int curIndex = 0;
Deque<Integer>stack = new Deque<>();
while(curIndex<n){
while(!stack.isEmpty() && height[curIndex] > height[stack.peek()]){
//弹出凹槽值
int top = stack.pop();
if(stack.isEmpty()) break;
int h = Math.min(height[stack.pop()],height[curIndex])-height[top];
int dist = curIndex-stack.peek()-1;
result += dist * h;
}
stack.push(curIndex++);
}
return result;
}
}
class Solution {
public int trap(int[] height) {
int ans = 0;
Deque<Integer> stack = new ArrayDeque<>();
int n = height.length;
int i = 0;
// for (int i = 0; i < n; ++i) {
while(i<n){
// height[i] > height[stack.peek()]得到一个可以接雨水的区域
while (!stack.isEmpty() && height[i] > height[stack.peek()]) {
int top = stack.pop(); // 获得接雨水位置的下标
if (stack.isEmpty()) { // 栈为空,左边不存在最大值,无法接雨水
break;
}
int left = stack.peek(); // left成为新的栈顶元素
int currWidth = i - left - 1; // 获取接雨水区域的宽度
int currHeight = Math.min(height[left], height[i]) - height[top];
ans += currWidth * currHeight;
}
stack.push(i++); // 在对下标i处计算能接的雨水量之后,将i入栈,继续遍历后面的下标
}
return ans;
}
}
84.柱状图中的最大的矩型
//创建一个单调递增栈
//面积等于右边界,也就是stack.pop() * 宽度:i - stack.peek() -1,其中左边界就是stack.peek();
class Solution {
public int largestRectangleArea(int[] heights) {
int n = heights.length;
//定义一个全局最大值
int max = 0;
Deque<Integer>stack = new ArrayDeque<>();
//出始栈底为-1
stack.push(-1);
for(int i = 0;i<n;i++){
while(stack.peek()!=-1 && heights[stack.peek()]>=heights[i]){
max = Math.max(max,heights[stack.pop()]*(i-stack.peek()-1));
}
stack.push(i);
}
while(stack.peek()!=-1){
max = Math.max(max,heights[stack.pop()]*(n-stack.peek()-1));
}
return max;
}
}
11.呈最多雨水的容器
这里采用双指针,对比接雨水这题相对来说是简单的,因为我们无非就是算面积最大值,而面积是最小值乘以两者的长度
class solution{
public int maxArea(int[] height) {
int left = 0;
int right = height.length-1;
int res = 0;
while(left < right){
//记录当前的高度值大小
int cur_val = Math.min(height[left],height[right]) * (right-left);
res = Math.max(cur_val,res);
if(height[left] < height[right]){
left++;
}else{
right--;
}
}
return res;
}
}
239滑动窗口最大值
- 观察滑动窗口的过程可以发现,实现单调队列必须使用支持在头部和尾部进行插入和删除的,很明显双链表是满足该条件的
- 单调队列和单调栈的核心思路没有变化,Push依旧是在队尾添加,但是要把前面比自己小的都删掉,保证加进来的是队列中最小的一个也就是[5 4 3 ] 如果6加进来就需要把前面的移除,但是如果是1呢,就可以直接加进去;维持一个单调递减的队列
- max方法就是获取队列首的元素
public int max(){
return q.getFirst();
}
- pop方法在队头删除元素n
public void pop(int n){
if(n == q.getFirst()){//因为第一个可能已经不复存在了
q.pollFirst();
}
}
- 有个小细节,linkedList支持在头部和尾部快速增删元素,而ArrayList结构在后序的取元素更适合
//方法1
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
//创建一个单调队列
int n= nums.length;
int[]res = new int[n-k+1];
Deque<Integer>deque =new ArrayDeque<>();
int index = 0;
for(int i = 0;i<k;i++){
//按升序进行排列
while(!deque.isEmpty() && deque.peekLast()<nums[i]){
deque.removeLast();
}
deque.offerLast(nums[i]);
}
res[index++]=deque.peekFirst();
for(int i = k;i<nums.length;i++){
if(nums[i-k]==deque.peekFirst()){
deque.removeFirst();
}
while(!deque.isEmpty()&&deque.peekLast()<nums[i]){
deque.removeLast();
}
deque.offerLast(nums[i]);
res[index++]=deque.peekFirst();
}
return res;
}
}
//方法进行封装
class Solution {
class MonotonicQueue{
LinkedList<Integer>q = new LinkedList<>();
public void push(int n){
while(!q.isEmpty() && q.getLast() < n){
q.pollLast();
}
q.addLast(n);
}
public void pop(int n){
if(n == q.getFirst()){
q.pollFirst();
}
}
public int max(){
return q.getFirst();
}
}
public int[] maxSlidingWindow(int[] nums, int k) {
MonotonicQueue window = new MonotonicQueue();
List<Integer>res = new ArrayList<>();
for(int i = 0;i<nums.length;i++){
//将前几个先装进去
if(i < k - 1){
window.push(nums[i]);
}else{
window.push(nums[i]);
res.add(window.max());
//移除旧数字
window.pop(nums[i-k+1]);
}
}
int[]arr = new int[res.size()];
for(int i = 0;i<res.size();i++){
arr[i] = res.get(i);
}
return arr;
}
}