https://leetcode.com/problems/next-greater-element-i/
题目大意是,给定两个数组nums1和nums2,前者是后者的子集。对于每个nums1里的元素,求它在nums2里的右边数字中第一个比它大的元素,若不存在则规定答案为-1。例如,若 n u m s 1 = [ 4 , 1 , 2 ] , n u m s 2 = [ 1 , 3 , 4 , 2 ] nums1 = [4,1,2], nums2 = [1,3,4,2] nums1=[4,1,2],nums2=[1,3,4,2],那么答案就是 [ − 1 , 3 , − 1 ] [-1,3,-1] [−1,3,−1]。这一道题是经典的单调栈的应用。基本思路是,将nums2的元素依次push进一个栈,并维护栈元素的单调递减性。也即,如果发现栈顶元素已经小于下一个要push的元素,那就可以认定这个栈顶元素所对应的下一个最大元素就是要push的这个元素,于是将栈顶pop出,直到栈空或者栈顶大于该元素。拿上面的例子举例,先push 1进栈,接着试图push 3进栈,发现栈顶元素1小于3,所以1的下一个最大元素就是3,那就将1 pop,此时栈空,再把3 push,紧接着发现4比3大,重复如此操作,pop 3 push 4,接下来由于push 2没有违反单调性,所以再push 2。每当pop元素的时候就用一个HashMap记录该元素的下一个最大元素,最后再整理返回。代码如下:
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;
class Solution {
public int[] nextGreaterElement(int[] nums1, int[] nums2) {
int[] res = new int[nums1.length];
// 特判
if (nums2 == null || nums2.length == 0) {
Arrays.fill(res, -1);
return res;
}
Stack<Integer> stack = new Stack<>();
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums2.length; i++) {
// 当栈顶元素小于将push的元素,就用map做记录并pop出
while (!stack.isEmpty() && stack.peek() < nums2[i]) {
map.put(stack.pop(), nums2[i]);
}
stack.push(nums2[i]);
}
for (int i = 0; i < nums1.length; i++) {
res[i] = map.getOrDefault(nums1[i], -1);
}
return res;
}
}
时间空间复杂度都为 O ( n u m s 2. l e n g t h ) O(nums2.length) O(nums2.length)。
算法复杂度证明只需注意map.put(stack.pop(), nums2[i]);
一共执行了 O ( n u m s 2. l e n g t h ) O(nums2.length) O(nums2.length)次。
算法正确性证明:
对于第一个循环,先证明一个结论:当循环 i i i结束时,有:
1,栈非空,元素严格单调递减
2,map里已经存储了 n u m s 2 [ 0 , . . . , i − 1 ] nums2[0,...,i-1] nums2[0,...,i−1]中所有next greater element在 n u m s 2 [ 1 , . . . , i ] nums2[1,...,i] nums2[1,...,i]里的那个数和它的next greater element组成的pair,且map的所有key都已经出栈。
数学归纳法:当 i = 0 i=0 i=0时,循环结束时 n u m s [ 0 ] nums[0] nums[0]进栈,map此时为空,两条性质都满足。假设当 i = k i=k i=k时结论成立,当 i = k + 1 i=k+1 i=k+1时,当循环结束时,由于比 n u m s [ k + 1 ] nums[k+1] nums[k+1]小于等于的元素都出栈了,由归纳假设,在 n u m s [ k + 1 ] nums[k+1] nums[k+1]进栈前,栈要么为空,要么也是单调递减的,所以它进栈后,非空和单调性都成立。对于性质2,当 i = k + 1 i=k+1 i=k+1的循环开始时,若栈为空,那么该循环结束时栈里只有一个元素,map没有新put一个entry,性质2满足;若栈不为空,由归纳假设中map的性质,栈内所有元素的next greater element都不在 n u m s 2 [ 0 , . . . , k ] nums2[0,...,k] nums2[0,...,k]里,此时若 n u m s [ k + 1 ] nums[k+1] nums[k+1]大于栈顶元素,那么栈顶元素的next greater element一定是 n u m s [ k + 1 ] nums[k+1] nums[k+1],于是栈顶元素出栈,并且map中put进这个元素和它的next greater element n u m s [ k + 1 ] nums[k+1] nums[k+1],如此操作,直到栈顶元素大于 n u m s [ k + 1 ] nums[k+1] nums[k+1](因为nums2元素彼此不同,所以这里是大于而不是大于等于),此时栈内元素的next greater element不在 n u m s 2 [ 0 , . . . , k + 1 ] nums2[0,...,k+1] nums2[0,...,k+1]中,并且map新put了所有next greater element是 n u m s [ k + 1 ] nums[k+1] nums[k+1]的pair,结合归纳假设,性质2成立。
算法的正确性由 i = n i=n i=n的循环结束时的性质2得出。证毕。