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

前言

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

相关推荐
NAGNIP22 分钟前
万字长文!回归模型最全讲解!
算法·面试
Irene199132 分钟前
Vue3 TypeScript 项目中,Emits 验证的使用场景
typescript·vue3·验证
知乎的哥廷根数学学派1 小时前
面向可信机械故障诊断的自适应置信度惩罚深度校准算法(Pytorch)
人工智能·pytorch·python·深度学习·算法·机器学习·矩阵
666HZ6662 小时前
数据结构2.0 线性表
c语言·数据结构·算法
实心儿儿3 小时前
Linux —— 基础开发工具5
linux·运维·算法
charlie1145141914 小时前
嵌入式的现代C++教程——constexpr与设计技巧
开发语言·c++·笔记·单片机·学习·算法·嵌入式
清木铎5 小时前
leetcode_day4_筑基期_《绝境求生》
算法
清木铎5 小时前
leetcode_day10_筑基期_《绝境求生》
算法
j_jiajia5 小时前
(一)人工智能算法之监督学习——KNN
人工智能·学习·算法
源代码•宸5 小时前
Golang语法进阶(协程池、反射)
开发语言·经验分享·后端·算法·golang·反射·协程池