JS Leetcode 496. 下一个更大元素 I 更清晰的图解单调栈做法

壹 ❀ 引

最近一周的工作压力很大...一周的时间一直在处理一个APP漏洞问题,因为项目三年无人维护,突然要改东西光是修改构建错误以及三方包依赖错误就花了三天时间= =。不过好在问题到已经结束尾,闲下来还是记录下最近的解题思路,本题来自LeetCode496. 下一个更大元素 I,难度属于简单,题目描述如下:

给你两个 没有重复元素 的数组 nums1 和 nums2 ,其中nums1 是 nums2 的子集。

请你找出 nums1 中每个元素在 nums2 中的下一个比其大的值。

nums1 中数字 x 的下一个更大元素是指 x 在 nums2 中对应位置的右边的第一个比 x 大的元素。如果不存在,对应位置输出 -1 。

示例 1:

输入: nums1 = [4,1,2], nums2 = [1,3,4,2].
输出: [-1,3,-1]
解释:
   对于 num1 中的数字 4 ,你无法在第二个数组中找到下一个更大的数字,因此输出 -1 。
   对于 num1 中的数字 1 ,第二个数组中数字1右边的下一个较大数字是 3 。
   对于 num1 中的数字 2 ,第二个数组中没有下一个更大的数字,因此输出 -1 。

示例 2:

输入: nums1 = [2,4], nums2 = [1,2,3,4].
输出: [3,-1]
解释:
   对于 num1 中的数字 2 ,第二个数组中的下一个较大数字是 3 。
   对于 num1 中的数字 4 ,第二个数组中没有下一个更大的数字,因此输出 -1 。

提示:

1 <= nums1.length <= nums2.length <= 1000
0 <= nums1[i], nums2[i] <= 104
nums1和nums2中所有整数 互不相同
nums1 中的所有整数同样出现在 nums2 中

贰 ❀ 暴力解法

简单提取下题目中的信息,首先数组nums1是数组nums2的子集,也就是子数组,且数组中不存在重复元素。题目要求是每次取nums1中的一个元素,去nums2中的与之相同的这个元素开始找,看看还有没有比它大的元素,如果有,记录这个元素,如果没有则记录为-1,最终返回包含这些结束的数组。

思路其实就很清晰了,我们可以先遍历nums1每次取一个元素,并查询此元素在nums2中的索引,那么nums2就可以从此索引+1的位置开始遍历,判断还有没有比num1[i]更大的元素,有就记录,无就记录-1即可。

大家可以先根据这个思路自己实现代码,毕竟还是有些细节需要自己感受,下面附上代码:

/**
 * @param {number[]} nums1
 * @param {number[]} nums2
 * @return {number[]}
 */
var nextGreaterElement = function (nums1, nums2) {
    let res = [];
    // 遍历nums1,每次取一个出来
    for (let i = 0; i < nums1.length; i++) {
        // 查找nums1[i]在nums2中的位置
        let index = nums2.indexOf(nums1[i]);
        // 需要注意的是这个元素可能是nums2中的最后一个,如果是最后一个就无法满足nums2的遍历条件,因此直接加入-1
        if (index === nums2.length - 1) {
            res.push(-1);
        };
        // 从查找到的索引+1位开始查找,有记录更大元素,无则记录-1
        for (let j = index + 1; j < nums2.length; j++) {
            if (nums2[j] > nums1[i]) {
                res.push(nums2[j]);
                // 因为无重复元素,找到更大的就跳出循环
                break;
            } else if (j === nums2.length - 1) {
                res.push(-1)
            };
        };
    };
    return res;
};

代码注释写的非常清楚,这里就不多解释了,我们接下来看看官方推荐的解题思路。

贰 ❀ 单调栈思路

当然,我在看单调栈题解之前,肯定是不知道这个思路以及做法的,这里的想法自然也是来自题解区的各位大佬。其中我在看到了LeetCode用户labuladong题解时觉得非常的形象也较好理解,这里借鉴下。

比如我们要求数组[2,1,2,4,3]中每个元素的下一个更大数,其实答案很明显是[4,2,4,-1,-1],要做到求出此结果,我们一样可以从当前元素往后遍历,一直遇到第一个比自己大的元素,若遍历完还没遇到就记录为-1级了。

其实有个很有趣的比喻就是,我们可以将数组中的每个元素理解为不同身高的人,然后每个人往后看,那么这个人的视线一定是被第一个比自己高的人给挡住,用图表示就是:

那么怎么用程序表示这个过程呢?这里就得借用单调栈了。我们都知道栈有FILO(先进后出)的特性。所谓单调栈,即是入栈时经过某种处理,让入栈后的元素满足单调递增或者单调递减的顺序,说直白点就是有序的栈。可能说到这还是比较模糊,我们直接针对[2,1,2,4,3]的例子给出一个代码,再去分析,我想这样会好理解一点,代码如下:

var nextGreaterElement = function (nums) {
    let res = [];
    let stack = [];
    // 因为栈是先进后出,所以倒序遍历反而满足了正向使用
    // 对应到比身高里面,我们都是往后看,那就从最后一个开始,一个接一个往后看,这也也便于知道谁是第一个比自己高的人
    for (let i = nums.length - 1; i >= 0; i--) {
        // 如果栈不为空,且栈顶第一个元素(对应到数组中最后一个元素)比当前nums[i]还矮就得弹出去
        while (stack.length && stack[stack.length - 1] <= nums[i]) {
            // 比nums[i]矮的人都弹出去
            // 因为nums是倒序遍历,每个人都是往后看比身高,假设nums[i]比后面的人还高,那么nums[i]前面的人一定只能看到
            // 自己,而看不到nums[i]后面的人,那么记录后面的人是没意义的,因为反正都会被nums[i]挡住,因此统统弹出去不记录。
            stack.pop();
        };
        // 记录第i个比自己更高的人
        // 因为上面的逻辑中,比nums[i]矮的人会被全部弹出栈,最坏的情况就是没有比自己高的,栈被清空了,因此为-1;
        // 而如果遇到比自己高的人是会停止弹出操作,所以栈顶第一个人一定是比自己高的人,也就是数组中最后一个人。
        res[i] = stack.length ? stack[stack.length - 1] : -1;
        // 记录nums[i],因为他可能是前面的人的第一个遇到比自己高的人,不高也没关系,反正会被弹出去
        stack.push(nums[i]);
    };
    return res;
};

大家可以尝试运行下这段代码,画图理解下,不理解没关系,下面我会画图解释这个过程:

第一次我们拿到了3,用3跟栈中做比较,结果栈是空的,没有能跟自己比较身高的人,行吧,那么3对应索引的答案就是-1,同时我们得把3加入栈中,因为很可能3是前面的人中,某个人能看到的第一个比自己高的人。

第二次比较,我们取到了4,一比较结果3还没4高,由于4是站在3前面的,4更前面的人最差就是看到自己,因为3被挡住了所以不可能被其他人看到,我们将3弹出去,此时栈又是空,对应的答案我们记录为-1,同时将4加入栈中,因为4可能是前面的人能看到的第一个更高的人。

第三次比较,我们取到了2,结果一比较2没有4高,由于栈此时不为空,那么栈顶就是比2高的人,答案记录为4,同时我们还是要将2加入栈,因为2也可能是前面的人能看到的第一个比自己高的人。

第四次比较,我们取到了1,它的情况跟第三次比较相同,没有2大,所以栈不为空,栈顶第一个就是比1大的人,记录答案为2,同时把1加入栈。

第五次比较,我们取到了2,跟栈顶第一个元素1进行比较,结果发现1没有2高,因此我们将1弹出栈。此时栈不为空,还有其他人可以继续和2比较,我们继续比,此时的栈顶是2,但2不能说比2更高,我们又得把栈顶的2弹出去,此时栈仍然不为空,继续比较。此时栈顶成了4,哎,4比2要大,那么答案记录为4,同时将2加入栈.

第六次比较已经不满足循环条件,跳出循环,我们得到了对应数组中每个元素的更大元素。

OK,那么到这里,我们详细介绍了借用单调栈解决上述例子的问题,那么如果解决文中的题目呢?其实仍然是一样的思路,题目中nums1nums2的子集,只要我们能得出nums2中每个比自己大的元素并记录起来,再遍历nums1不就能直接得出答案了吗?

直接上代码:

var nextGreaterElement = function (nums1, nums2) {
    let res = [];
    let stack = [];
    // 用于记录nums2中每个比自己大的值
    let map = new Map();
    for (let i = nums2.length - 1; i >= 0; i--) {
        while (stack.length && stack[stack.length - 1] <= nums2[i]) {
            stack.pop();
        };
        // 记录i对应的值,便于后续nums1查找
        map.set(nums2[i], stack.length ? stack[stack.length - 1] : -1);
        stack.push(nums2[i]);
    };
    for (let i = 0; i < nums1.length; i++) {
        // 直接根据key取结果就好了
        res.push(map.get(nums1[i]));
    };
    return res;
};

那么到这里本文结束。

原文地址:https://www.cnblogs.com/echolun/p/14590131.html