leetcode-day11

leetcode hot 100

滑动窗口最大值

描述

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

解题思路

1、暴力求解,双层遍历;优化:如果新进入窗口的数大于上一个窗口的最大值,则本窗口最大值为新进入的数。否则,如果从滑动窗口出去的那个数不是上一个窗口的最大值,则此窗口最大值等于上一个窗口最大值,否则本窗口需要一次循环来计算最大值。优化后还是超时。。。复杂度为O(n^2)。
2、优先队列,尝试写了下还是超时,代码贴了如下,思考了一下应该是remove方法不是O(1)的,需要进一步优化,优化方法就是在队列中添加元素的索引信息,如果堆顶元素的索引超过了滑动窗口的范围,则从堆中弹出,剩余的堆顶元素就是窗口内的最大值,复杂度为O(nlogn),因为入堆操作复杂度为O(logn)。
3、单调队列,类似于双指针的思想,队列中存放的元素是数组的索引,新加入队列的元素可以淘汰对应数组值比他小的所有元素,新加入的队列总是放在队尾;队首元素需要判断是否超出窗口范围。时间复杂度为O(n)。
4、分块+预处理。
我们可以将数组 nums 从左到右按照 kkk 个一组进行分组,最后一组中元素的数量可能会不足 k个。如果我们希望求出 nums[i] 到 nums[i+k−1] 的最大值,就会有两种情况:
如果 i是 k 的倍数,那么 nums[i]到 nums[i+k−1]恰好是一个分组。我们只要预处理出每个分组中的最大值,即可得到答案;
如果 i不是 k 的倍数,那么 nums[i]到 nums[i+k−1]会跨越两个分组,占有第一个分组的后缀以及第二个分组的前缀。假设 j是 k的倍数,并且满足 i<j≤i+k−1,那么 nums[i]到 nums[j−1]就是第一个分组的后缀,nums[j] 到 nums[i+k−1]就是第二个分组的前缀。如果我们能够预处理出每个分组中的前缀最大值以及后缀最大值,同样可以在 O(1)的时间得到答案。
思想与稀疏表相似,稀疏表解决的是RMQ区间最值问题,一般采用动态规划的方式建表。

代码段

// Java
// 优先队列
class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        int[] res = new int[nums.length - k + 1];
        // 维护一个长度为k的队列,从大到小
        PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(Comparator.reverseOrder());
        for (int i = 0; i < k; ++i) {
            priorityQueue.offer(nums[i]);
        }
        res[0] = priorityQueue.peek();
        for (int i = 1; i <= nums.length - k; ++i) {
            priorityQueue.remove(nums[i-1]);
            priorityQueue.offer(nums[i+k-1]);
            res[i] = priorityQueue.peek();
        }
        return res;
    }
}

// 优化remove方法
class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        int[] res = new int[nums.length - k + 1];
        // 维护一个长度为k的队列,从大到小
        PriorityQueue<int[]> priorityQueue = new PriorityQueue<>(new Comparator<int[]>() {
            @Override
            public int compare(int[] o1, int[] o2) {
                return o2[0] == o1[0] ? o2[1] - o1[1] : o2[0] - o1[0];
            }
        });
        for (int i = 0; i < k; ++i) {
            priorityQueue.offer(new int[]{nums[i], i});
        }
        res[0] = priorityQueue.peek()[0];
        for (int i = 1; i <= nums.length - k; ++i) {
            while (!priorityQueue.isEmpty() && priorityQueue.peek()[1] < i) {
                priorityQueue.poll();
            }
            priorityQueue.offer(new int[]{nums[i + k - 1], i + k - 1});
            res[i] = priorityQueue.peek()[0];
        }
        return res;
    }
}
//单调队列
class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        int n = nums.length;
        Deque<Integer> deque = new LinkedList<Integer>();
        for (int i = 0; i < k; ++i) {
            while (!deque.isEmpty() && nums[i] >= nums[deque.peekLast()]) {
                deque.pollLast();
            }
            deque.offerLast(i);
        }

        int[] ans = new int[n - k + 1];
        ans[0] = nums[deque.peekFirst()];
        for (int i = k; i < n; ++i) {
            while (!deque.isEmpty() && nums[i] >= nums[deque.peekLast()]) {
                deque.pollLast();
            }
            deque.offerLast(i);
            while (deque.peekFirst() <= i - k) {
                deque.pollFirst();
            }
            ans[i - k + 1] = nums[deque.peekFirst()];
        }
        return ans;
    }
}

// 分块+预处理(稀疏表法)
class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        int n = nums.length;
        int[] prefixMax = new int[n];
        int[] suffixMax = new int[n];
        for (int i = 0; i < n; ++i) {
            if (i % k == 0) {
                prefixMax[i] = nums[i];
            }
            else {
                prefixMax[i] = Math.max(prefixMax[i - 1], nums[i]);
            }
        }
        for (int i = n - 1; i >= 0; --i) {
            if (i == n - 1 || (i + 1) % k == 0) {
                suffixMax[i] = nums[i];
            } else {
                suffixMax[i] = Math.max(suffixMax[i + 1], nums[i]);
            }
        }

        int[] ans = new int[n - k + 1];
        for (int i = 0; i <= n - k; ++i) {
            ans[i] = Math.max(suffixMax[i], prefixMax[i + k - 1]);
        }
        return ans;
    }
}
// Kotlin

// Python

// Go

// JavaScript

// C++

 // Rust
 

技巧总结

区间最值问题多想想稀疏表法,复杂度可降低至O(logn)

链接

题目

滑动窗口最大值

题解

题解

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
LeetCode-Editor是一种在线编码工具,它提供了一个用户友好的界面编写和运行代码。在使用LeetCode-Editor时,有时候会出现乱码的问题。 乱码的原因可能是由于编码格式不兼容或者编码错误导致的。在这种情况下,我们可以尝试以下几种解决方法: 1. 检查文件编码格式:首先,我们可以检查所编辑的文件的编码格式。通常来说,常用的编码格式有UTF-8和ASCII等。我们可以将编码格式更改为正确的格式。在LeetCode-Editor中,可以通过界面设置或编辑器设置来更改编码格式。 2. 使用正确的字符集:如果乱码是由于使用了不同的字符集导致的,我们可以尝试更改使用正确的字符集。常见的字符集如Unicode或者UTF-8等。在LeetCode-Editor中,可以在编辑器中选择正确的字符集。 3. 使用合适的编辑器:有时候,乱码问题可能与LeetCode-Editor自身相关。我们可以尝试使用其他编码工具,如Text Editor、Sublime Text或者IDE,看是否能够解决乱码问题。 4. 查找特殊字符:如果乱码问题只出现在某些特殊字符上,我们可以尝试找到并替换这些字符。通过仔细检查代码,我们可以找到导致乱码的特定字符,并进行修正或替换。 总之,解决LeetCode-Editor乱码问题的方法有很多。根据具体情况,我们可以尝试更改文件编码格式、使用正确的字符集、更换编辑器或者查找并替换特殊字符等方法来解决这个问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值