LeetCode 239. Sliding Window Maximum 题解(下)

15 篇文章 0 订阅

LeetCode 239. Sliding Window Maximum 题解(下)

题目描述


Given an array nums, there is a sliding window of size k which is moving from the very left of the array to the very right. You can only see the k numbers in the window. Each time the sliding window moves right by one position.

For example,
Given nums = [1,3,-1,-3,5,3,6,7], and k = 3.

Window position       Max
————— —–
[1 3 -1] -3 5 3 6 7       3
1 [3 -1 -3] 5 3 6 7       3
1 3 [-1 -3 5] 3 6 7       5
1 3 -1 [-3 5 3] 6 7       5
1 3 -1 -3 [5 3 6] 7       6
1 3 -1 -3 5 [3 6 7]       7
Therefore, return the max sliding window as [3,3,5,5,6,7].

Note:
You may assume k is always valid, ie: 1 ≤ k ≤ input array’s size for non-empty array.

Follow up:
Could you solve it in linear time?


题目分析

这题看到后真的感觉是这样的: 要过应该不难, 但是要达到它说的Follow Up 中的条件,就不简单了。
为什么会这么感觉呢?首先看一遍题意: 就是求对于一个数组里面每个连续的k个数的最大值构成的数组。
下面是几种方法:
1. 最笨的方法: 对于每个数遍历他所包含的k个数, 找到最大就行了。复杂度: O(nk)O(k)
2. 一般学过数据结构的人的想法: 建立一个大小为k的大根堆(C++就用优先队列即可)。 并维护之即可。复杂度: O(nk)O(logk)
3. 利用 496. Next Greater Element I 所得到的结论求解。(即本题解的上篇)复杂度: O(n)

这次直接开门见山的说了。 1,2 的思路, 具体都不会在这里写了(因为一下子写上下两篇真的很累 =。=) 重点挑3方法详细写。

声明:想到这个方法有一半要归功于我同学。 我一个人估计是想不出来。

首先,我们来把这两道题建立一定的联系。 现在来换个对这道题要求的表述:对于一个数组的连续的长度为k的子数组subArray, 若原数组中大于subArray[0]的下一个元素所在位置已经超出了subArray, 则subArray的最大值即为subArray[0]. 否则subArray的最大值必定大于等于 subArray[greaterIndex[0]], 于是对subArray[greaterIndex[0]] 进行相同检测, 直到subArray[greaterIndex[j]] 超出subArray。此时subArray[j]即为此子数组的最大元素。

这就是用前面那道题解决这道题的核心思想。 尽管写出来了, 但是肯定非常不好理解。。。 下面用例子说明。

Example: nums = [1,3,-1,-3,5,3,6,7], and k = 3

首先可以求出其greaterIndex数组, 表示的是下一个更大的元素所在的位置(下标index)。如下:
greaterIndex = [1, 4, 4, 4, 6, 6, 7, -1](-1即表示不存在后续更大的值)
我们开始搜素 index=0 , 发现greaterIndex[0] = 1 小于等于此时 subArray 最大Index = 0 + 3 - 1(一般情况即为 n+k1 ).继续发现greaterIndex[1] = 4 > 3因此可以退出搜索认为 index=1 就是该window的最大值。

一个个index讲太费篇幅, 挑一个重要的, 搜索 index=5 的 3 来进行分析。
注意到此时最大值指针已经在 index=6 上了。为什么呢? 前一步搜索 index=4 的时候跳过去的, 而这个指针只会往前跳, 不会往回走! 这是等会儿分析复杂度的一个重要依据。
既然已经在 index=6 , 那么就查找greaterIndex[6] = 7 <= 5 + 3 - 1, 所以应该把最大值指针跳向 index=7 . 还要继续! 查找到 greaterIndex[7] = -1, 结合-1的含义, 可以确定这个 subArraynums[7] .

注意这时候循环应该结束! 因为再向后数组元素个数已经不足k个了。

以上就是算法的详细描述。 应该不是很难理解。 我是觉得重点在于如何想到利用前面那道题的结论来放到这道题上。

贴代码之前, 我们先分析一下这种方法的复杂度。分析的过程其实也就是进一步完善我们思路细节的过程。
对于这种算法, 首先有一个计算greaterIndex数组的过程。这个的复杂度为 O(n) .
然后其实里面有两重循环, 但分析之后我们可以看到复杂度仍然是线性的。
首先是对数组每个元素要处理, 这是一重循环。无法避免的。
接下来是对于每个元素, 要让max指针一直跳, 直到它出了subArray的范围或者greaterIndex[j] = -1(即当前已确定为最大)。这又是一个while循环。
但是需要注意的是, while循环里的变量是最大值指针j,不会往回跳的。 这就说明, 在执行完所有的外层循环, 内层循环最多一共执行 n1 次。

具体还有很多细节方面的处理。 就不展开说明了。 代码中可以很明显的看得出需要对哪些进行处理。
下面是具体代码实现:

class Solution {
public:
    vector<int> nextGreaterElement(vector<int>& nums) {
        vector<int> greaterIndex(nums.size(), -1);
        stack<int> s;
        for(int i = 0; i < nums.size(); ++i) {
            while (!s.empty() && nums[s.top()] < nums[i]) {
                greaterIndex[s.top()] = i;
                s.pop();
            }
            s.push(i);
        }
        return greaterIndex;
    }
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        if (k == 0) return vector<int>();
        vector<int> greaterIndex = nextGreaterElement(nums);
        cout << endl;
        vector<int> ans(nums.size() - k + 1);
        int j = 0; //max value pointer.
        for (int i = 0; i < ans.size(); ++i) {
            while (greaterIndex[j]!= -1 && greaterIndex[j] < i + k) {
                j = greaterIndex[j];
            }
            ans[i] = nums[j];
            if (j == i) ++j;
        }
        return ans;
    }
};

其他细节

  1. 先做前面那道Easy题或者说先看这份题解的前半部分,就很容易弄懂nextGreaterElement()函数是在干什么。而且可以把这个函数与之前那题的函数进行比对, 只是稍作修改后直接拿来用的。
  2. 处理空向量。这个是老生常谈了。
  3. 注意这一句:if (j == i) ++j;可以思考一下为什么要这样写。 如果不理解,参考下面这个数据的情况:
    nums = [1,-1], k = 1

一点感受:
这次一道Hard分了两篇写, 真的累。。。。(事实上还有一道可以算Medium难度的Easy….) 同时也感觉到了其实有些Easy 啊, Medium啊就是为 Hard 铺路用的。 这些也不能忽视了。 所以之后可能打算若干题Easy 和 Medium 合起来写一篇。 或者是分专题写也有可能(毕竟我们上课上的就是专题嘛。。:)

累死了, 以上。

The End.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值