经典算法之滑动窗口-暴力解法+单调队列解法

作用:通常求最大(小)子数组/子序列/值

package com.xch.niuke;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

/**
 * 滑动窗口
 *
 * @author XuChenghe
 * @date 2023/8/27 12:18
 */
public class Main001 {
    
    public static void main(String[] args) {
        // 最小值暴力解法
        int[] res1 = f1(new int[]{3, 1, 4, 5, 2, 6, 7}, 3);
        System.out.println(Arrays.toString(res1));
        // 最大值暴力解法
        int[] res2 = f2(new int[]{3, 1, 4, 5, 2, 6, 7}, 3);
        System.out.println(Arrays.toString(res2));
        
        // 最小值单调队列(递增)解法
        int[] res3 = f3(new int[]{3, 1, 4, 5, 2, 6, 7}, 3);
        System.out.println(Arrays.toString(res3));
        // 最大值单调队列(递减)解法
        int[] res4 = f4(new int[]{3, 1, 4, 5, 2, 6, 7}, 3);
        System.out.println(Arrays.toString(res4));
    }
    
    /**
     * 最小值暴力解法
     *
     * @param nums
     * @param k
     * @return
     */
    public static int[] f1(int[] nums, int k) {
        int[] res = new int[nums.length - k + 1];
        for (int i = k - 1; i < nums.length; i++) {
            res[i - k + 1] = nums[i];
            for (int j = i; j > i - k; j--) {
                if (nums[j] < res[i - k + 1]) {
                    res[i - k + 1] = nums[j];
                }
            }
        }
        return res;
    }
    
    /**
     * 最大值暴力解法
     *
     * @param nums
     * @param k
     * @return
     */
    public static int[] f2(int[] nums, int k) {
        int[] res = new int[nums.length - k + 1];
        for (int i = k - 1; i < nums.length; i++) {
            res[i - k + 1] = nums[i];
            for (int j = i; j > i - k; j--) {
                if (nums[j] > res[i - k + 1]) {
                    res[i - k + 1] = nums[j];
                }
            }
        }
        return res;
    }
    
    /**
     * 最小值单调队列(递增)解法
     *
     * @param nums
     * @param k
     * @return
     */
    public static int[] f3(int[] nums, int k) {
        List<Integer> list = new LinkedList<>();// 用List集合模拟Queue队列
        int[] res = new int[nums.length - k + 1];
        for (int i = 0; i < nums.length; i++) {
            if (i < k - 1) {
                putAndPop3(null, list, nums[i]);
            } else if (i == k - 1) {
                putAndPop3(null, list, nums[i]);
                res[i - k + 1] = list.get(0);
            } else {
                putAndPop3(nums[i - k], list, nums[i]);
                res[i - k + 1] = list.get(0);
            }
        }
        return res;
    }
    
    /**
     * 维护单调队列(递增)
     *
     * @param head
     * @param list
     * @param num
     */
    public static void putAndPop3(Integer head, List<Integer> list, int num) {
        if (head != null && list.get(0) == head) {
            list.remove(0);
        }
        for (int i = list.size() - 1; i >= 0; i--) {
            if (list.get(i) > num) {// 注意相同的不要清除,否则有异常:-3, 4, 5, 8, 0, -1, 0, 1, 2, 0, 3, 4
                list.remove(i);
            } else {
                break;
            }
        }
        list.add(num);
    }
    
    /**
     * 最大值单调队列(递减)解法
     *
     * @param nums
     * @param k
     * @return
     */
    public static int[] f4(int[] nums, int k) {
        List<Integer> list = new LinkedList<>();// 用List集合模拟Queue队列
        int[] res = new int[nums.length - k + 1];
        for (int i = 0; i < nums.length; i++) {
            if (i < k - 1) {
                putAndPop4(null, list, nums[i]);
            } else if (i == k - 1) {
                putAndPop4(null, list, nums[i]);
                res[i - k + 1] = list.get(0);
            } else {
                putAndPop4(nums[i - k], list, nums[i]);
                res[i - k + 1] = list.get(0);
            }
        }
        return res;
    }
    
    /**
     * 维护单调队列(递减)
     *
     * @param head
     * @param list
     * @param num
     */
    public static void putAndPop4(Integer head, List<Integer> list, int num) {
        if (head != null && list.get(0) == head) {
            list.remove(0);
        }
        for (int i = list.size() - 1; i >= 0; i--) {
            if (list.get(i) < num) {// 注意相同的不要清除,否则有异常
                list.remove(i);
            } else {
                break;
            }
        }
        list.add(num);
    }
    
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: p1886 滑动窗口滑动窗口是一种常用的算法,用于解决一些区间相关的问题。它的基本思想是维护一个大小固定的窗口,窗口每次向右移动一个单位,同时更新窗口内的信息。这个算法的时间复杂度通常是 O(n)。 【模板】单调队列单调队列是一种特殊的队列,它的队首和队尾都是单调的。在使用单调队列时,我们通常需要维护一个单调递增或单调递减的队列。单调队列可以用来解决一些区间最值相关的问题,例如滑动窗口的最大值或最小值等。单调队列的时间复杂度通常是 O(n)。 ### 回答2: P1886滑动窗口是一道比较经典的题目,也是单调队列的一个很好的练习题目。这道题目大致分为两个步骤:首先,我们需要用一个循环来遍历整个数组;其次,我们需要用一个双端队列(即单调队列)来储存当前窗口内的元素,并维护队列中的单调性,最后将最大值输出。 我们可以将数组想象成一个长度为 $n$ 的窗口,其中每个元素代表着这个窗口内的一个数,然后随着窗口的滑动,这个窗口内的元素也会不断地变换。因此,我们需要用循环来遍历整个数组,每次将窗口向右移动一个单位。 对于每个窗口,我们需要用一个双端队列(即单调队列)来维护队列中的单调性,并找出最大值。这里的单调性指队列中的元素呈单调递减或单调递增的关系。为了实现这个目标,我们需要在处理每个元素时,将其与队列末尾的元素进行比较: 如果当前元素 $a_i$ 大于队列末尾的元素,则说明队列末尾的元素不可能成为最大值,因此我们可以将其从队列中删除; 如果当前元素 $a_i$ 小于队列末尾的元素,则将其加入队列的末尾; 如果当前元素 $a_i$ 等于队列末尾的元素,则我们不需要将其加入队列,因为它的位置可以被后面的元素取代。 在以上处理完之后,队列的头部元素即为目前窗口内的最大值,每次我们将其输出即可。由于每个元素最多只有进队和出队各一次,因此总时间复杂度为 $O(n)$。 在实现这个算法时,需要注意队列存储的是下标而不是元素,这样才能判断队列末尾的元素是否在窗口内。同时,队列中存储的是一个递减的序列,对于相等的元素就不需要再次加入队列了。 ### 回答3: p1886 滑动窗口 /【模板】单调队列是一道经典算法问题,其通过建立单调队列来实现滑动窗口的计算。 滑动窗口是一种非常常见的算法,它主要是在一个序列中滑动一个长度固定的窗口,以便对窗口内的元素进行计算。在这个过程中,每次将窗口向右移动一步,依次计算窗口内的元素。 滑动窗口的应用非常广泛,比如在在字符串匹配、时间序列分析、区间动态规划等领域都能见到其身影。 而通过单调队列来实现滑动窗口计算的方法主要是通过维护一个单调递减的队列来实现,在滑动窗口向右移动时,如果有新的元素进入窗口,就将队列末端所有比新元素大的元素弹出。这样保证队列存储的都是当前窗口内的最小值。 具体代码实现可以参考如下: ```c++ #include <iostream> #include <deque> using namespace std; int a[100005]; int n, k; void MonotonicQueue() { deque<int> q; for (int i = 0; i < n; i++) { //保证队列末端存的是当前窗口内的最小值 while (!q.empty() && q.front() <= i - k) q.pop_front(); while (!q.empty() && a[i] < a[q.back()]) q.pop_back(); q.push_back(i); if (i >= k - 1) cout << a[q.front()] << ' '; } } int main() { cin >> n >> k; for (int i = 0; i < n; i++) cin >> a[i]; MonotonicQueue(); cout << endl; return 0; } ``` 在这段代码里,我们先建立一个空的单调队列,并在for循环里遍历整个序列,每次将新元素加入队列时,先将末端所有比新元素大的元素弹出,以保证队列末端存储当前窗口内的最小元素,然后再将新元素加入到队列末端。当窗口长度达到k时,输出队列开头的元素,即为当前窗口内的最小值。 总体来说,p1886 滑动窗口 /【模板】单调队列是一道比较好理解的算法问题,大家可以多练习一下,掌握滑动窗口算法的精髓,以及单调队列的应用方法。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BB-X

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值