一、前言
今天想要分享的题目其实和之前写的股票价格那道题所用到的结构是一样的,就是单调栈,因为自己当时也对这种结构不太熟悉,看到评论区很多大佬说如果遇到要找下一个最大/最小这一类的题目,可以往单调栈这一块儿去想,这次,我们就通过LeetCode496-下一个更大元素 I和LeetCode503-下一个更大元素 II来强化一下对单调栈的影响。
二、LeetCode496
①暴力破解
首先这道题我们可以暴力解决,时间复杂度是O(n^2),思路很简单,我们遍历nums1,找到nums1元素和nums2元素相等的位置j(题目提到nums1的元素一定会在nums2出现),此时我们定义k=j+1;即相等的元素的后面一个元素。然后从k开始遍历nums2,如果找到nums2[k]大于nums1i 的元素,则记录nums2[k]进入结果数组,反之记录-1.
那么我们的代码如下:
class Solution {
public int[] nextGreaterElement(int[] nums1, int[] nums2) {
int m = nums1.length, n = nums2.length;
int[] res = new int[m];
for (int i = 0; i < m; i++) {
int j = 0;
//寻找nums1和num2元素相等的位置
while (j < n && nums1[i] != nums2[j]) {
j++;
}
int k = j + 1;
//从k开始遍历,在k<n的前提下寻找nums2[k] > nums1[i]的元素
while (k < n && nums2[k] < nums2[j]) {
k++;
}
//找到了返回该元素,没找到返回-1
res[i] = k < n ? nums2[k] : -1;
}
return res;
}
}
②单调栈
那么我们有没有O(n)级别的解决方法呢,答案是有的,我们可以通过单调栈的方法解决,上一节leetCode题目我们曾经讲解过单调栈的定义,单调栈是栈中元素严格递增或者严格递减的一种特殊的栈结构,每当新元素插入时,都需要判断该元素与栈顶元素的大小关系,不断弹出栈顶元素直到满足单调性。这里不做多说。
那么这道题我们该如何使用单调栈解决这个问题呢,答案很简单,我们首先对nums2下手
我们要找比当前元素右边更大的元素,那么我们其实倒过来想是更好的,因为这样可以保证我们每一个元素确确实实的都处理到了。如果正向,你其实单调栈反而不好维护。因为你不知道你的下一个元素是什么,会不会比你前一个要大,你可能就会频繁的覆盖原来的结果。或者你正向也可以,当你发现比栈顶大的,你就记录结果,但是这样的话,你会发现你所有元素都会入栈,这样不符合单调栈的思想。
那么我们继续按照倒过来的思想解决:我们把它想成一个单调递减的栈,从nums2最后一个元素开始遍历,如果该元素比栈顶元素更大,就出栈,此时我们在入栈新的元素,保持栈为单调递减的。你会发现,每当进新元素的时候,只要此时栈不为空,说明存在比该元素右边第一个更大的元素,如果栈为空,那么就直接记录-1.那么我们该怎么记录呢,答案很简单,使用哈希表即可,我们哈希表每次记录以遍历的元素为key,value则是上面的逻辑,根据栈是否是空,value取栈顶或者-1;,然后将遍历的元素入栈。至此,一次循环完毕,遍历第二个元素,以此类推。
最后我们以nums1的每个元素为key,在哈希表找对应value即可。
那测试用例nums1 = [4,1,2], nums2 = [1,3,4,2]来画图举个例子:
根据上述逻辑,我们返回res结果为[-1,3,-1].
代码如下:
class Solution {
public int[] nextGreaterElement(int[] nums1, int[] nums2) {
int m = nums1.length;
int n = nums2.length;
HashMap<Integer,Integer> hashRes = new HashMap<>();
Deque<Integer> arrayDeque = new ArrayDeque<>();
int[] res = new int[m];
for (int i = n - 1;i >= 0; i--) {
while (!arrayDeque.isEmpty() && nums2[i]>arrayDeque.peekLast()){
arrayDeque.pollLast();
}
hashRes.put(nums2[i],arrayDeque.isEmpty()?-1:arrayDeque.peekLast());
arrayDeque.addLast(nums2[i]);
}
for (int i = 0; i < res.length; i++) {
res[i] = hashRes.get(nums1[i]);
}
return res;
}
}
这样做时间复杂度是O(m+n) -> O(n)
三、LeetCode503
那么做完上面那道题后,我们再来看一下这道题的变种,我们发现现在就一个数组了,但是这个数组变成循环数组了。
但其实这道题非常简单,我们首先想法是什么,我们可以把这个循环数组想成一个2倍长的数组,相当于把原数组复制粘贴两次到一个2倍长的数组,例如[1,2,1]就变成[1,2,1,1,2,1],这样可以很方便我们后续进行操作。然后核心思想仍然没变,单调栈,且我们仍然和上题一样,从新的数组末尾开始遍历,如果该元素比栈顶元素更大,就出栈,此时我们在入栈新的元素,保持栈为单调递减的。
然后此时其实就可以不用哈希表了,之前使用哈希表是为了将Nums的位置映射到Nums1;再一个原因是此时是可以存在重复元素了,不像上题不存在重复的元素,现在我们可以直接new一个数组装新数组的结果这个新数组的长度可以是2倍数组的长度也可以是原数组的长度。如果是2倍数组的长度最后缩容即可,原数组的长度,我们可以通过(i%originalLength)去更新res的值,结果是一样的。数数组初始化时我们可以先让它的每一个元素值均为默认值-1,然后就是将上题放哈希表那一步替换为res的值的相关操作。
例如拿[1,2,1]举例,我们先通过程序复制我们的数组变成[1,2,1,1,2,1],然后new一个res,长度我用原来的数组好了,即new int[3].然后具体效果如下图:
根据上述逻辑,我们返回res=[2,-1.2]
代码如下:
Java
class Solution {
public int[] nextGreaterElements(int[] nums) {
//边界判断
if (nums == null || nums.length <= 1) {
return new int[]{-1};
}
int originalLength = nums.length;
int[] doubledArray = new int[2 * originalLength];
//复制数组
for (int i = 0; i < originalLength; i++) {
doubledArray[i] = nums[i];
doubledArray[i + originalLength] = nums[i];
}
int[] result = new int[originalLength];//存放结果
Arrays.fill(result, -1);//默认全部初始化为-1
Deque<Integer> stack = new ArrayDeque<>();
for (int i = doubledArray.length - 1; i >= 0; i--){
while (!stack.isEmpty() && doubledArray[i] >= stack.peek()){
stack.pop();
}
result[i % originalLength] = stack.isEmpty()?-1:stack.peek();
stack.push(doubledArray[i]);
}
return result;
}
}
那么对于上述的代码,我们可否优化呢,其实是可以的,我们其实可以免去复制数组这个步骤的
我们从刚才获取result的思想可以想到通过取余的操作去解决这个问题,即我们直接遍历原数组,只不过我们的i是从2倍数组的长度-1开始,然后每次新的元素变成nums[i%orignalLength],所以在解题思路不变的情况下,可以将上面代码变成如下做法:
class Solution {
public int[] nextGreaterElements(int[] nums) {
int originalLength = nums.length;
int[] result = new int[originalLength];//存放结果
Arrays.fill(result, -1);//默认全部初始化为-1
Deque<Integer> stack = new ArrayDeque<>();
for (int i = 2*originalLength - 1; i >= 0; i--){
while (!stack.isEmpty() && nums[i%originalLength] >= stack.peek()){
stack.pop();
}
result[i % originalLength] = stack.isEmpty()?-1:stack.peek();
stack.push(nums[i%originalLength]);
}
return result;
}
}