滑动窗口最大值问题的解决方法及代码实现

2025/3/15
本文详细介绍了滑动窗口最大值问题的三种解决方法:暴力法、双端队列法和堆法,并提供了相应的JavaScript代码实现。暴力法适用于小规模数据,双端队列法是最优解,堆法适用于动态维护最大值的场景。
一个程序员在电脑前编写代码,屏幕上显示滑动窗口最大值问题的代码实现

滑动窗口最大值问题是一个经典的算法问题,通常用于解决在给定数组中,找到每个固定大小的滑动窗口中的最大值。这个问题可以通过多种方法来解决,包括暴力法、使用双端队列(Deque)等。下面我将详细介绍这些方法,并给出相应的代码实现。

1. 暴力法

暴力法的思路是对于每个窗口,遍历窗口内的所有元素,找到最大值。这种方法的时间复杂度为 O(n*k),其中 n 是数组的长度,k 是窗口的大小。

function maxSlidingWindow(nums, k) {
    const result = [];
    for (let i = 0; i <= nums.length - k; i++) {
        let max = nums[i];
        for (let j = 1; j < k; j++) {
            if (nums[i + j] > max) {
                max = nums[i + j];
            }
        }
        result.push(max);
    }
    return result;
}

2. 使用双端队列(Deque)

双端队列的方法可以在 O(n) 的时间复杂度内解决这个问题。其核心思想是维护一个双端队列,队列中存储的是数组元素的索引,并且保证队列中的元素是递减的。这样,队列的头部就是当前窗口的最大值。

function maxSlidingWindow(nums, k) {
    const result = [];
    const deque = []; // 存储索引

    for (let i = 0; i < nums.length; i++) {
        // 移除不在当前窗口的元素
        if (deque.length > 0 && deque[0] < i - k + 1) {
            deque.shift();
        }

        // 移除所有小于当前元素的元素
        while (deque.length > 0 && nums[deque[deque.length - 1]] < nums[i]) {
            deque.pop();
        }

        // 将当前元素的索引加入队列
        deque.push(i);

        // 如果窗口已经形成,将队列头部的元素加入结果
        if (i >= k - 1) {
            result.push(nums[deque[0]]);
        }
    }

    return result;
}

3. 使用堆(Heap)

另一种方法是使用堆(优先队列)来维护窗口中的最大值。这种方法的时间复杂度为 O(n log k),因为每次插入和删除操作都需要 O(log k) 的时间。

function maxSlidingWindow(nums, k) {
    const result = [];
    const maxHeap = new MaxHeap();

    for (let i = 0; i < nums.length; i++) {
        maxHeap.push(nums[i]);
        if (i >= k - 1) {
            result.push(maxHeap.peek());
            maxHeap.remove(nums[i - k + 1]);
        }
    }

    return result;
}

// 实现一个最大堆
class MaxHeap {
    constructor() {
        this.heap = [];
    }

    push(val) {
        this.heap.push(val);
        this.bubbleUp(this.heap.length - 1);
    }

    peek() {
        return this.heap[0];
    }

    remove(val) {
        const index = this.heap.indexOf(val);
        if (index === -1) return;
        this.swap(index, this.heap.length - 1);
        this.heap.pop();
        this.bubbleDown(index);
    }

    bubbleUp(index) {
        while (index > 0) {
            const parentIndex = Math.floor((index - 1) / 2);
            if (this.heap[parentIndex] >= this.heap[index]) break;
            this.swap(parentIndex, index);
            index = parentIndex;
        }
    }

    bubbleDown(index) {
        while (true) {
            const leftChildIndex = 2 * index + 1;
            const rightChildIndex = 2 * index + 2;
            let largestIndex = index;

            if (leftChildIndex < this.heap.length && this.heap[leftChildIndex] > this.heap[largestIndex]) {
                largestIndex = leftChildIndex;
            }

            if (rightChildIndex < this.heap.length && this.heap[rightChildIndex] > this.heap[largestIndex]) {
                largestIndex = rightChildIndex;
            }

            if (largestIndex === index) break;
            this.swap(index, largestIndex);
            index = largestIndex;
        }
    }

    swap(i, j) {
        [this.heap[i], this.heap[j]] = [this.heap[j], this.heap[i]];
    }
}

总结

  • 暴力法:简单直观,但时间复杂度较高,适用于小规模数据。
  • 双端队列:时间复杂度为 O(n),是最优解,适用于大规模数据。
  • :时间复杂度为 O(n log k),适用于需要动态维护最大值的场景。

在实际应用中,双端队列的方法是最常用的解决方案,因为它既高效又易于实现。

上次更新:

相关文章

npx完全指南:前端开发必备工具详解 | 20年架构师深度解析

本文由20年前端架构师深入解析npx工具,涵盖其核心功能、优势、高级用法、最佳实践及与npm/yarn的区别比较,帮助开发者掌握这一现代前端开发利器。

·前端开发

Astro 静态站点生成器:构建高性能网站的最佳选择

Astro 是一个专注于构建快速、轻量级网站的静态站点生成器,支持多种前端框架,采用岛屿架构减少 JavaScript 加载,提升性能。

·前端开发

MySQL JSON数据类型支持与使用指南 | 详细解析与示例

本文详细解析了MySQL从5.7版本开始支持的JSON数据类型,包括版本支持、创建JSON字段、插入与查询JSON数据、修改JSON数据、生成JSON、索引优化、性能与应用场景、注意事项及示例全流程。

·后端开发

Weex 跨平台移动开发框架:核心特性与使用指南

Weex 是由阿里巴巴开源的跨平台移动开发框架,支持使用 Vue.js 或 Rax 构建高性能的 iOS、Android 和 Web 应用。本文详细解析了 Weex 的核心特性、架构、工作流程、组件和模块、开发工具、优缺点、应用场景及未来发展。

·前端开发

ECharts 与 DataV 数据可视化工具对比分析 | 选择指南

本文详细对比了 ECharts 和 DataV 两个常用的数据可视化工具,包括它们的设计目标、优缺点、使用场景和技术栈,帮助读者根据具体需求选择合适的工具。

·前端开发

前端部署后通知用户刷新页面的常见方案 | 单页应用更新提示

本文介绍了在前端部署后通知用户刷新页面的几种常见方案,包括WebSocket实时通知、轮询检查版本、Service Worker版本控制、版本号对比、自动刷新、使用框架内置功能以及第三方库。每种方案的优缺点和示例代码均有详细说明。

·前端开发

TypeScript 映射类型常见问题与解决方案 | 提升代码维护性

本文探讨了在使用 TypeScript 时,映射类型的不当使用可能导致的问题,如代码难以维护、类型推断不准确或性能问题,并提供了相应的解决方案和最佳实践。

·编程语言

TypeScript 交叉类型与联合类型:区别与最佳实践

本文详细解释了 TypeScript 中交叉类型(Intersection Types)和联合类型(Union Types)的区别,提供了使用场景、类型守卫、避免过度使用交叉类型的建议,以及如何通过工具辅助解决混淆问题。

·编程语言

TypeScript 类继承中的常见类型问题及解决方案 | TypeScript 开发指南

本文详细探讨了在 TypeScript 中使用类继承时可能遇到的常见类型问题,包括类型兼容性、构造函数、方法重写、访问修饰符、泛型类继承、抽象类以及类型断言等问题,并提供了相应的解决方案和代码示例。

·编程语言

TypeScript 函数重载:常见问题与解决方案

本文探讨了 TypeScript 中函数重载的常见问题,包括签名与实际实现不匹配、重载签名过多、与泛型结合时的类型推断问题等,并提供了相应的解决方案。

·编程语言