算法细节系列(10):503. Next Greater Element II

Next Greater Element 系列

看了一些leetcode官方解和discuss,解释的不太满意,重新按照自己的思路梳理一遍,争取把它优化的过程阐述清楚。

496. Next Greater Element I

Problem:

You are given two arrays (without duplicates) nums1 and nums2 where nums1’s elements are subset of nums2. Find all the next greater numbers for nums1’s elements in the corresponding places of nums2.

The Next Greater Number of a number x in nums1 is the first greater number to its right in nums2. If it does not exist, output -1 for this number.

Example 1:

Input: nums1 = [4,1,2], nums2 = [1,3,4,2].
Output: [-1,3,-1]
Explanation:
For number 4 in the first array, you cannot find the next greater number for it in the second array, so output -1.
For number 1 in the first array, the next greater number for it in the second array is 3.
For number 2 in the first array, there is no next greater number for it in the second array, so output -1.

Example 2:

Input: nums1 = [2,4], nums2 = [1,2,3,4].
Output: [3,-1]
Explanation:
For number 2 in the first array, the next greater number for it in the second array is 3.
For number 4 in the first array, there is no next greater number for it in the second array, so output -1.

Note:

  • All elements in nums1 and nums2 are unique.
  • The length of both nums1 and nums2 would not exceed 1000.

像这样的题,我一开始想都没想,针对nums1的每个元素,循环遍历找nums2中的next greater。很明显它的复杂度就是 O(nm) ,代码如下:

public int[] nextGreaterElement(int[] findNums, int[] nums) {

        int[] ans = new int[findNums.length];
        for (int i = 0; i < findNums.length; i++) {
            ans[i] = -1;
            boolean canFind = false;
            for (int key : nums) {
                if (key == findNums[i]) {
                    canFind = true;
                }
                if (canFind && key > findNums[i]) {
                    ans[i] = key;
                    break;
                }
            }
        }

        return ans;
    }

这是我最初的版本,虽然AC了,但总觉得还可以优化,的确如此,看了官方的discuss之后,发现了一种 O(n) 的解决方案。为什么能从一个 O(n2) 的时间复杂度降低到 O(n)

优化思路

数组中的所有数,你可以想象成绵延曲折的山路,遍历可以想象成上山下山的过程,而找寻next greater可以看成在当前的视野中,你所能看到的下一个山峰。

上述代码就是一种最naive的做法,每当我们在当前点时,都需要派一个分身继续赶路,他下山的过程你没有记录,自然看不到,而当他第一次出现在你的视野当中时,你就知道了next greater了,那你就前进一步,重复上述操作。你看着不累,可你的小分身们,可是累死累活的帮你干活啊。

所以上述代码的主要问题很明显了,分身下山过程是无状态的,你没有很好的记录这些信息,但这些信息难道没用么?

再来看一种情况,如果你一直处于上山的阶段,你所派出的分身可以很简单,每当它只走一步时,你就能发现next greater,与其这样,你还不如自己走,是吧。但此时你犹豫了,我咋知道下一步是上还是下呢,好吧,我把当前的状态记录在小本子上吧,恰巧当我到了下一个状态后,你发现比原来高,行,我把之前状态的next greater更新为最新达到的peek。

好了,不编故事了,刚才已经引出了优化的思路,原先代码没有状态的记录,此时我们要做的就是把当前状态给记录下来。那么怎么记呢?记的顺序如何?

刚才还指出了分身在下山过程中,对于主人来说,它所遍历的路径也是不清楚的,那么我们就拿个本子记录它的遍历路径咯。这样,每当下山的时候,因为我不清楚下一个山峰到底在哪里,所以我打个问号,日后把答案补上,那么什么时候开始记答案呢?很明显,当我第一次往上走的时候!而我们知道,可能第一次往上,你就乘上了直升梯,直奔一个很高的peek。那么,你开始想了,嗯,我应该开始更新我之前记录的状态了,为了确保更新不出错,我得倒着来,一个个比较,谁叫那时候你在下山呢。

public int[] nextGreaterElement(int[] findNums, int[] nums) {
        int[] ans = new int[findNums.length];


        Map<Integer,Integer> map = new HashMap<>();
        Stack<Integer> stack = new Stack<>();

        for (int num : nums){

            while(!stack.isEmpty() && stack.peek() < num)
                map.put(stack.pop(), num);
            stack.push(num);
        }

        for (int i = 0; i < findNums.length; i++){
            ans[i] = map.getOrDefault(findNums[i], -1);
        }

        return ans;
    }

for循环中的过程就是我叙述的那个过程,如果想从代码理解上述过程,自己调试下即可。

503. Next Greater Element II

Problem:

Given a circular array (the next element of the last element is the first element of the array), print the Next Greater Number for every element. The Next Greater Number of a number x is the first greater number to its traversing-order next in the array, which means you could search circularly to find its next greater number. If it doesn’t exist, output -1 for this number.

Example 1:

Input: [1,2,1]
Output: [2,-1,2]
Explanation: The first 1’s next greater number is 2;
The number 2 can’t find next greater number;
The second 1’s next greater number needs to search circularly, which is also 2.

Note:
The length of given array won’t exceed 10000.

这道题和上面本质上是一样的,也可以在 O(n) 内完成,它是一个循环找peek问题,但没关系,复制一份同样的数组,放在它的后面就好了。

public int[] nextGreaterElements(int[] nums) {
        int len = nums.length;
        int[] ans = new int[len];
        Arrays.fill(ans, -1);

        Stack<Integer> stack = new Stack<>();
        for (int i = 0; i < 2 * len; i++){
            int num = nums[i % len];
            while(!stack.isEmpty() && nums[stack.peek()] < num)
                ans[stack.pop()] = num;

            if (i < len) stack.push(i);
        }

        return ans;
    }

这个for循环过程含义更加清楚,num表示当前我所在的位置,而stack记录的就是之前我所走过的状态。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值