代码随想录算法训练营第五十八天-单调栈1|739. 每日温度 496.下一个更大元素 I
739. Daily Temperatures
public class DailyTemperatures {//暴力解法public int[] dailyTemperatures(int[] T){int length = T.length;int[] result = new int[length];for(int i = 0; i < length; i++){int current = T[i];if(current < 100){for(int j = i + 1; j < length; j++){if(T[j] > current){result[i] = j - i;break;}}}}return result;}
思路
首先想到的当然是暴力解法,两层for循环,把至少需要等待的天数就搜出来了。时间复杂度是O(n^2)
那么接下来在来看看使用单调栈的解法。
739图解
那有同学就问了,我怎么能想到用单调栈呢? 什么时候用单调栈呢?
通常是一维数组,要寻找任一个元素的右边或者左边第一个比自己大或者小的元素的位置,此时我们就要想到可以用单调栈了。时间复杂度为O(n)。
例如本题其实就是找找到一个元素右边第一个比自己大的元素,此时就应该想到用单调栈了。
那么单调栈的原理是什么呢?为什么时间复杂度是O(n)就可以找到每一个元素的右边第一个比它大的元素位置呢?
单调栈的本质是空间换时间,因为在遍历的过程中需要用一个栈来记录右边第一个比当前元素高的元素,优点是整个数组只需要遍历一次。
更直白来说,就是用一个栈来记录我们遍历过的元素,因为我们遍历数组的时候,我们不知道之前都遍历了哪些元素,以至于遍历一个元素找不到是不是之前遍历过一个更小的,所以我们需要用一个容器(这里用单调栈)来记录我们遍历过的元素。
在使用单调栈的时候首先要明确如下几点:
- 单调栈里存放的元素是什么?
单调栈里只需要存放元素的下标i就可以了,如果需要使用对应的元素,直接T[i]就可以获取。
- 单调栈里元素是递增呢? 还是递减呢?
注意以下讲解中,顺序的描述为 从栈头到栈底的顺序,因为单纯的说从左到右或者从前到后,不说栈头朝哪个方向的话,大家一定比较懵。
这里我们要使用递增循序(再强调一下是指从栈头到栈底的顺序),因为只有递增的时候,栈里要加入一个元素i的时候,才知道栈顶元素在数组中右面第一个比栈顶元素大的元素是i。
我们要保持一个递增单调栈(从栈头到栈底),所以将T[0]弹出(元素记录过就可以从栈里弹出),T[1]加入,此时result数组可以记录了,result[0] = 1,即T[0]右面第一个比T[0]大的元素是T[1]。
即:如果求一个元素右边第一个更大元素,单调栈就是递增的,如果求一个元素右边第一个更小元素,单调栈就是递减的。
文字描述理解起来有点费劲,接下来我画了一系列的图,来讲解单调栈的工作过程,大家再去思考,本题为什么是递增栈。
使用单调栈主要有三个判断条件。
- 当前遍历的元素T[i]小于栈顶元素T[st.top()]的情况
- 当前遍历的元素T[i]等于栈顶元素T[st.top()]的情况
- 当前遍历的元素T[i]大于栈顶元素T[st.top()]的情况
把这三种情况分析清楚了,也就理解透彻了。
public int[] dailyTemperatures(int[] temperatures) {int lens = temperatures.length;int[]res = new int[lens];/如果当前遍历的元素 大于栈顶元素,表示 栈顶元素的 右边的最大的元素就是 当前遍历的元素,所以弹出 栈顶元素,并记录如果栈不空的话,还要考虑新的栈顶与当前元素的大小关系否则的话,可以直接入栈。注意,单调栈里 加入的元素是 下标。*/Deque<Integer> stack = new LinkedList<>();stack.push(0);for(int i = 1;i < lens; i++){if(temperatures[i]<=temperatures[stack.peek()]){stack.push(i);}else{while(!stack.isEmpty()&&temperatures[i] >temperatures[stack.peek()]){res[stack.peek()] = i - stack.peek();stack.pop();}stack.push(i);}}return res;
496.下一个更大元素 I
1.数组nums1是nums2的子集,且各自没有重复的元素
2.求数组nums1里的元素在数组nums2里,这个元素,它的右边第一个比其大的元素,输出放入对应位置
思路:
1)从题目示例中我们可以看出最后是要求nums1的每个元素在nums2中下一个比当前元素大的元素,那么就要定义一个和nums1一样大小的数组result来存放结果。题目说如果不存在对应位置就输出 -1 ,所以result数组如果某位置没有被赋值,那么就应该是是-1,所以就初始化为-1
2)在nums2里找某元素大于右边第一个比他大元素数,所以遍历数组2.
3)nums1和nums2的联系,找到nums1[i] == nums2[j]对应的下标,用hashmap映射
4)单调栈是递增
import java.util.Arrays;
import java.util.HashMap;
import java.util.Stack;public class NextGreaterElement1 {public int[] nextGreaterElement(int[] nums1, int[] nums2) {Stack<Integer> temp = new Stack<>();int[] res = new int[nums1.length];// To fill complete array with a particular valueArrays.fill(res, -1);//对nums1进行映射,用哈希表HashMap<Integer, Integer> hashMap = new HashMap<>();for(int i = 0; i < nums1.length; i++){hashMap.put(nums1[i], i);}//用单调栈遍历数组2temp.add(0);for(int i = 1; i< nums2.length; i++){if(nums2[i] <= nums2[temp.peek()]){temp.add(i);}else{while (!temp.isEmpty()&& nums2[i] > nums2[temp.peek()]){if(hashMap.containsKey(nums2[temp.peek()])){//获取对应下标Integer index = hashMap.get(nums2[temp.peek()]);//结果更新res[index] = nums2[i];}temp.pop();}temp.add(i);}}return res;}
}