返回

用滑动窗口最大值征服 LeetCode 的困难挑战

前端

各位尊贵的读者们,欢迎来到我技术博客的殿堂!今天,我们将踏上一段激动人心的旅程,探索算法领域最令人望而生畏的挑战之一——滑动窗口最大值。作为一名技术博客创作专家,我将引领你们踏上征服 LeetCode 这座算法高峰的征程,并在途中为你们奉上独树一帜的见解和深入浅出的讲解。

前言

算法是计算机科学领域的基石,而 LeetCode 是一个备受推崇的在线算法竞赛平台。LeetCode 汇集了海量的算法题目,涵盖初级、中级和困难三个难度等级,为算法爱好者和竞赛选手提供了绝佳的磨练场所。今天,我们将把目光投向 LeetCode 困难级别的“滑动窗口最大值”问题,并提供两种解法,助力读者征服这座算法高峰。

问题

滑动窗口最大值问题是这样的:给定一个整型数组和一个窗口大小 k,要求找出数组中所有滑动窗口中的最大值。滑动窗口是指数组中连续的 k 个元素,而滑动窗口最大值是指窗口中最大的元素。

解法一:暴力双指针法

暴力双指针法是一种朴素而直观的解法。具体步骤如下:

  1. 设置两个指针 left 和 right,分别指向窗口的左边界和右边界。
  2. 在 [left, right] 这个滑动窗口中,找到最大值。
  3. 将最大值存储到结果数组中。
  4. 移动 right 指针,扩大窗口。
  5. 如果 right 指针超出了数组边界,移动 left 指针,缩小窗口。
  6. 重复步骤 2-5,直到 right 指针到达数组末尾。

复杂度分析 :暴力双指针法的平均时间复杂度为 O(n^2),其中 n 是数组的长度。对于每个窗口,它需要 O(n) 的时间找到最大值。由于有 n 个窗口,因此总的时间复杂度为 O(n^2)。

解法二:双端队列法

双端队列(也称 Deque)是一种特殊的数据结构,支持在两端进行插入和删除操作。双端队列法利用了 Deque 的先进先出(FIFO)特性,可以高效地求解滑动窗口最大值问题。

具体步骤如下:

  1. 初始化一个双端队列 dq。
  2. 将窗口中的元素依次添加到 dq 中。
  3. 当 right 指针移动时,如果 dq 队首的元素不在当前窗口中,则将其弹出。
  4. 在将新元素添加到 dq 之前,弹出 dq 中所有比新元素小的元素。
  5. 此时,dq 队首的元素就是当前窗口的最大值。
  6. 将最大值存储到结果数组中。
  7. 重复步骤 3-6,直到 right 指针到达数组末尾。

复杂度分析 :双端队列法的平均时间复杂度为 O(n),其中 n 是数组的长度。对于每个窗口,它只需要 O(1) 的时间找到最大值。由于有 n 个窗口,因此总的时间复杂度为 O(n)。

代码示例

下面分别用 Java、Python 和 C++ 三种语言实现双端队列法:

Java

import java.util.ArrayDeque;
import java.util.Deque;

public class SlidingWindowMaximum {

    public int[] maxSlidingWindow(int[] nums, int k) {
        if (nums == null || nums.length == 0) {
            return new int[0];
        }

        Deque<Integer> dq = new ArrayDeque<>();
        int[] result = new int[nums.length - k + 1];

        for (int i = 0; i < nums.length; i++) {
            // 弹出所有比当前元素小的元素
            while (!dq.isEmpty() && nums[dq.peekLast()] < nums[i]) {
                dq.pollLast();
            }

            // 弹出队首元素,如果不在窗口内
            if (!dq.isEmpty() && dq.peekFirst() <= i - k) {
                dq.pollFirst();
            }

            // 将当前元素加入队尾
            dq.offerLast(i);

            // 记录当前窗口的最大值
            if (i >= k - 1) {
                result[i - k + 1] = nums[dq.peekFirst()];
            }
        }

        return result;
    }
}

Python

from collections import deque

def maxSlidingWindow(nums, k):
    if not nums or len(nums) == 0:
        return []

    dq = deque()
    result = []

    for i, num in enumerate(nums):
        # 弹出所有比当前元素小的元素
        while dq and nums[dq[-1]] < num:
            dq.pop()

        # 弹出队首元素,如果不在窗口内
        if dq and dq[0] <= i - k:
            dq.popleft()

        # 将当前元素加入队尾
        dq.append(i)

        # 记录当前窗口的最大值
        if i >= k - 1:
            result.append(nums[dq[0]])

    return result

C++

#include <deque>
#include <vector>

using namespace std;

vector<int> maxSlidingWindow(vector<int>& nums, int k) {
    if (nums.empty()) {
        return {};
    }

    deque<int> dq;
    vector<int> result;

    for (int i = 0; i < nums.size(); i++) {
        // 弹出所有比当前元素小的元素
        while (!dq.empty() && nums[dq.back()] < nums[i]) {
            dq.pop_back();
        }

        // 弹出队首元素,如果不在窗口内
        if (!dq.empty() && dq.front() <= i - k) {
            dq.pop_front();
        }

        // 将当前元素加入队尾
        dq.push_back(i);

        // 记录当前窗口的最大值
        if (i >= k - 1) {
            result.push_back(nums[dq.front()]);
        }
    }

    return result;
}

总结

在本文中,我们深入探究了滑动窗口最大值问题,并提供了暴力双指针法和双端队列法的两种解法。双端队列法凭借其出色的性能,成为解决此类问题的首选方法。通过采用先进先出的特性,双端队列法可以有效地维护滑动窗口中的最大值,从而将时间复杂度降低到 O(n)。我们还提供了 Java、Python 和 C++ 三种语言的代码示例,帮助读者更好地理解和应用这些解法。

希望这篇文章对各位读者有所帮助。如果您有任何疑问或建议,欢迎随时在评论区留言。让我们共同探索算法世界的奥秘,不断提升我们的技术能力!