【前端学算法】以为是白给的困难题结果我白给了

前言

leetcode链接:

239. 滑动窗口最大值

实现声明~~(叠甲)~~,我只是个小菜鸡,leetcode总共就完成了100来题,大多数是简单的那种,遇到这题是因为我最近在刷滑动窗口的题目,我以为很简单,结果光速白给,大家看个乐子。

题目详情

给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

返回 滑动窗口中的最大值

示例 1:

ini 复制代码
输入: nums = [1,3,-1,-3,5,3,6,7], k = 3
输出: [3,3,5,5,6,7]
解释:
滑动窗口的位置                  最大值
---------------               -----
[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

示例 2:

ini 复制代码
输入: nums = [1], k = 1
输出: [1]

白给暴力解法

第一想法是困难题就这?不就是左指针(i)遍历nums,然后右指针从(i -> i+k)编辑nums,记录每次遍历的最大值存起来,直接解决。

ts 复制代码
function maxSlidingWindow(nums: number[], k: number): number[] {
    let ans:number[] = [];

    for(let left = 0; left < nums.length - k + 1; left++) {
        let right = left;
        let tempAns = -Infinity;
        while(right < left + k) {
            tempAns = Math.max(tempAns, nums[right]);
            right++;
        }
        ans.push(tempAns);
    }

    return ans;
};

然后就直接白给了

失败总结

算了下,用例是长度为64317的数组,滑动窗口的长度是32879

在这段代码中,外层循环运行次数为 nums.length - k + 1 次,内层循环运行次数为 k 次。因此,总的运行次数为 (nums.length - k + 1) * k 次,那复杂度约为为O(n * k)

怪不得会超时,这段代码得优化到至少 O(n) 才能通过。

思考过程

1、如果要优化到 O(n) ,那么里面的那层k次循环的必须优化成 O(1)

2、那么我们就要思考这层内循环的意思是什么,这层内循环是为了获取滑动窗口内的最大值。

分类讨论

但是我们是否真的需要循环去遍历得到最大值?其实是不需要的,因为这里面有个规律,我们可以从示例中得到。

ini 复制代码
输入: nums = [1,3,-1,-3,5,3,6,7], k = 3
输出: [3,3,5,5,6,7]
解释:
滑动窗口的位置                  最大值
---------------               -----
[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

假设我们滑动窗口的边界坐标是[i, j],会获得一个最大值,我们记为 r0

然后接着开始滑动,窗口的边界坐标是[i+1, j+1],记移动后新出现的数是 n1,那么会有以下情况:

1、r0 > n1,且r0对应的坐标处于[i+1, j+1],那么此时的最大值r1 = r0

2、r0 > n1,且r0不处于[i+1, j+1],那么此时最大值为[i,j]之中非r0的数值n1之间产生;

3、r0 <= n1,那么此时的最大值r1 = n1;

构建递减数组:

1、3这种情况都一目明了,只有2需要思考一下怎么处理。

2这种情况的话,是一个数组中在O(1)时间内找到最大值,那么自然想到,如果这个数组是递减或递增的,那么从 0 或者 length-1 这两个位置就能拿到最大值。

那么如何构建这个这个玩意呢,假设我们构建的是 递减的 数组,那么我们需要做的事情就很明确:

每次push前,判断被push的元素是否比数组内的元素大,有的话,就把那些元素给踢出去。

那么,你就能得到一个递减的数组。

单调队列解法

ts 复制代码
function maxSlidingWindow(nums: number[], k: number): number[] {
    
    const result: number[] = [];
    
    //  存储坐标的单调队列
    //  为什么存坐标,是因为我们还要记录坐标是否越界了,就存nums[i]无法知道是否越界
    const queue:number[] = [];
    
    //  i可以理解为右指针
    for(let i = 0; i < nums.length; i++) {
        
        //  判断被push的元素是否比数组内的元素大,有的话,就把那些元素给踢出去
        //  这边从末尾踢是因为递减的,末尾元素一定是最小的,
        //  从小开始比较可以一直用queue.length-1作为比较坐标,不需要额外的变量去记录
        while (queue.length > 0 && nums[i] >= nums[queue[queue.length-1]]) {
            queue.pop();
        }

        queue.push(i);
        
        //  如果移动后最大值越界了,所以要把最大值踢出去
        if (i - queue[0] >= k) {
            queue.shift();
        }
        
        //  记录答案,右指针要大于等于k-1的时候才形成宽度为k的窗口
        if (i >= k-1) {
            //  队首就是最大值
            result.push(nums[queue[0]]);
        }
    }

    return result;
};

提交结果

结语

做了几道滑动窗口的题目后,有那么一点点感觉,但是又没有完全掌握,属实折磨,大概这就是之前一直不乐意刷leetcode的原因罢~~(自食其果)~~。

相关题目链接:

3. 无重复字符的最长子串

76. 最小覆盖子串

相关推荐
浅念-5 小时前
递归解题指南:LeetCode经典题全解析
数据结构·算法·leetcode·职场和发展·排序算法·深度优先·递归
Kiling_07045 小时前
Java集合进阶:Set与Collections详解
算法·哈希算法
智者知已应修善业6 小时前
【51单片机89C51及74LS273、74LS244组成】2022-5-28
c++·经验分享·笔记·算法·51单片机
洛水水6 小时前
【力扣100题】33.验证二叉搜索树
算法·leetcode·职场和发展
SimpleLearingAI6 小时前
聚类算法详解
算法·数据挖掘·聚类
刀法如飞7 小时前
Go 字符串查找的 20 种实现方式,用不同思路解决问题
算法·面试·程序员
Dlrb12119 小时前
C语言-指针数组与数组指针
c语言·数据结构·算法·指针·数组指针·指针数组·二级指针
WL_Aurora9 小时前
Python 算法基础篇之集合
python·算法
平行侠9 小时前
A15 工业路由器IP前缀高速检索与内存压缩系统
网络·tcp/ip·算法
阿旭超级学得完10 小时前
C++11包装器(function和bind)
java·开发语言·c++·算法·哈希算法·散列表