单调栈模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| int[] calculateGreaterElement(int[] nums) { int n = nums.length; int[] res = new int[n]; Stack<Integer> s = new Stack<>(); for (int i = n - 1; i >= 0; i--) { while (!s.isEmpty() && s.peek() <= nums[i]) { s.pop(); } res[i] = s.isEmpty() ? -1 : s.peek(); s.push(nums[i]); } return res; }
|
这就是单调队列解决问题的模板。for 循环要从后往前扫描元素,因为我们借助的是栈的结构,倒着入栈,其实是正着出栈。while 循环是把两个「个子高」元素之间的元素排除,因为他们的存在没有意义,前面挡着个「更高」的元素,所以他们不可能被作为后续进来的元素的下一个更大元素了。
分析它的时间复杂度,要从整体来看:总共有 n 个元素,每个元素都被 push 入栈了一次,而最多会被 pop 一次,没有任何冗余操作。所以总的计算规模是和元素规模 n 成正比的,也就是 O(n) 的复杂度。
LeetCode 496.下一个更大的元素Ⅰ
题目说 nums1 是 nums2 的子集,那么我们先把 nums2 中每个元素的下一个更大元素算出来存到一个映射里,然后再让 nums1 中的元素去查表即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class Solution { public int[] nextGreaterElement(int[] nums1, int[] nums2) { int[] greater = calculateGreaterElement(nums2); HashMap<Integer, Integer> greaterMap = new HashMap<>(); for (int i = 0; i < nums2.length; i++) { greaterMap.put(nums2[i], greater[i]); } int[] res = new int[nums1.length]; for (int i = 0; i < nums1.length; i++) { res[i] = greaterMap.get(nums1[i]); } return res; }
int[] calculateGreaterElement(int[] nums) { } }
|
LeetCode 739.每日温度
这个问题本质上也是找下一个更大元素,只不过现在不是问你下一个更大元素的值是多少,而是问你当前元素距离下一个更大元素的索引距离而已。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 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; } }
|
LeetCode 503.下一个更大的元素Ⅱ
这个问题肯定还是要用单调栈的解题模板,但难点在于,比如输入是 [2,1,2,4,3],对于最后一个元素 3,如何找到元素 4 作为下一个更大元素。
对于这种需求,常用套路就是将数组长度翻倍:
这样,元素 3 就可以找到元素 4 作为下一个更大元素了,而且其他的元素都可以被正确地计算。
有了思路,最简单的实现方式当然可以把这个双倍长度的数组构造出来,然后套用算法模板。但是,我们可以不用构造新数组,而是利用循环数组的技巧来模拟数组长度翻倍的效果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class Solution { public int[] nextGreaterElements(int[] nums) { int n = nums.length; int[] res = new int[n]; Stack<Integer> s = new Stack<>(); for (int i = 2 * n - 1; i >= 0; i--) { while (!s.isEmpty() && s.peek() <= nums[i % n]) { s.pop(); } res[i % n] = s.isEmpty() ? -1 : s.peek(); s.push(nums[i % n]); } return res; } }
|
LeetCode 1019.链表中的下一个更大节点
这道题输入的是一条单链表,我们把它转化成数组,方便用索引访问即可直接套用
单调栈模板 中的 nextGreaterElement 函数逻辑。
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[] nextLargerNodes(ListNode head) { ArrayList<Integer> nums = new ArrayList<>(); for (ListNode p = head; p != null; p = p.next) { nums.add(p.val); } int[] res = new int[nums.size()]; Stack<Integer> stk = new Stack<>(); for (int i = nums.size() - 1; i >= 0; i--) { while (!stk.isEmpty() && stk.peek() <= nums.get(i)) { stk.pop(); } res[i] = stk.isEmpty() ? 0 : stk.peek(); stk.push(nums.get(i)); } return res; } }
|
LeetCode 1944.队列中可以看到的人数
这道题显然要用到
单调栈技巧:靠左的高个子可以把靠右相邻的矮个子都「挤掉」,相当于计算下一个更大元素,即
单调栈的几种模板实现 中的 nextGreaterElement 函数。
只不过这道题不是问你下一个更大元素是多少,而是问你当前元素和下一个更大元素之间的元素个数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class Solution { public int[] canSeePersonsCount(int[] heights) { int n = heights.length; int[] res = new int[n]; Stack<Integer> stk = new Stack<>(); for (int i = n - 1; i >= 0; i--) { int count = 0; while (!stk.isEmpty() && heights[i] > stk.peek()) { stk.pop(); count++; } res[i] = stk.isEmpty() ? count : count + 1; stk.push(heights[i]); } return res; } }
|
LeetCode 1475.商品折扣后的最终价格
这道题就用到了
单调栈的几种模板实现 中讲到的一个单调栈模板:计算下一个更小或相等的元素。
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
| class Solution { public int[] finalPrices(int[] prices) { int n = prices.length; int[] res = new int[n]; int[] nextElement = nextLessOrEqualElement(prices); for (int i = 0; i < prices.length; i++) { if (nextElement[i] != -1) { res[i] = prices[i] - nextElement[i]; } else { res[i] = prices[i]; } } return res; }
int[] nextLessOrEqualElement(int[] nums) { int n = nums.length; int[] res = new int[n]; Stack<Integer> s = new Stack<>(); for (int i = n - 1; i >= 0; i--) { while (!s.isEmpty() && s.peek() > nums[i]) { s.pop(); } res[i] = s.isEmpty() ? -1 : s.peek(); s.push(nums[i]); } return res; } }
|