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

前言

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. 最小覆盖子串

相关推荐
岁忧2 分钟前
(LeetCode 面试经典 150 题 ) 11. 盛最多水的容器 (贪心+双指针)
java·c++·算法·leetcode·面试·go
chao_7895 分钟前
二分查找篇——搜索旋转排序数组【LeetCode】两次二分查找
开发语言·数据结构·python·算法·leetcode
秋说2 小时前
【PTA数据结构 | C语言版】一元多项式求导
c语言·数据结构·算法
Maybyy2 小时前
力扣61.旋转链表
算法·leetcode·链表
卡卡卡卡罗特4 小时前
每日mysql
数据结构·算法
chao_7895 小时前
二分查找篇——搜索旋转排序数组【LeetCode】一次二分查找
数据结构·python·算法·leetcode·二分查找
lifallen6 小时前
Paimon 原子提交实现
java·大数据·数据结构·数据库·后端·算法
lixzest6 小时前
C++ Lambda 表达式详解
服务器·开发语言·c++·算法
EndingCoder6 小时前
搜索算法在前端的实践
前端·算法·性能优化·状态模式·搜索算法
丶小鱼丶6 小时前
链表算法之【合并两个有序链表】
java·算法·链表