Chiriri's blog Chiriri's blog
首页
  • Java

    • JavaSE
    • JavaEE
    • 设计模式
  • Python

    • Python
    • Python模块
    • 机器学习
  • Golang

    • Golang
    • gRPC
  • 服务器

    • Linux
    • MySQL
    • NoSQL
    • Kubernetes
  • 项目

    • 传智健康
    • 畅购商城
  • Hadoop生态

    • Hadoop
    • Zookeeper
    • Hive
    • Flume
    • Kafka
    • Azkaban
    • Hbase
    • Scala
    • Spark
    • Flink
  • 大数据项目

    • 离线数仓
  • 青训营

    • 第四届青训营
  • HTML

    • HTML
    • JavaScript
  • Vue

    • Vue2
    • TypeScript
    • Vue3
    • Uni-APP
  • 数据结构与算法
  • C语言
  • 考研数据结构
  • 计算机组成原理
  • 计算机操作系统
  • Java基础

    • Java基础
    • Java集合
    • JUC
    • JVM
  • 框架

    • Spring
    • Dubbo
    • Spring Cloud
  • 数据库

    • MySQL
    • Redis
    • Elasticesearch
  • 消息队列

    • RabbitMQ
    • RocketMQ
  • 408

    • 计算机网络
    • 操作系统
    • 算法
  • 分类
  • 标签
  • 归档
  • 导航站
GitHub (opens new window)

Iekr

苦逼后端开发
首页
  • Java

    • JavaSE
    • JavaEE
    • 设计模式
  • Python

    • Python
    • Python模块
    • 机器学习
  • Golang

    • Golang
    • gRPC
  • 服务器

    • Linux
    • MySQL
    • NoSQL
    • Kubernetes
  • 项目

    • 传智健康
    • 畅购商城
  • Hadoop生态

    • Hadoop
    • Zookeeper
    • Hive
    • Flume
    • Kafka
    • Azkaban
    • Hbase
    • Scala
    • Spark
    • Flink
  • 大数据项目

    • 离线数仓
  • 青训营

    • 第四届青训营
  • HTML

    • HTML
    • JavaScript
  • Vue

    • Vue2
    • TypeScript
    • Vue3
    • Uni-APP
  • 数据结构与算法
  • C语言
  • 考研数据结构
  • 计算机组成原理
  • 计算机操作系统
  • Java基础

    • Java基础
    • Java集合
    • JUC
    • JVM
  • 框架

    • Spring
    • Dubbo
    • Spring Cloud
  • 数据库

    • MySQL
    • Redis
    • Elasticesearch
  • 消息队列

    • RabbitMQ
    • RocketMQ
  • 408

    • 计算机网络
    • 操作系统
    • 算法
  • 分类
  • 标签
  • 归档
  • 导航站
GitHub (opens new window)
  • 数据结构

    • 位运算
    • 最基本的数据结构
    • 前缀和数组
    • random的随机行为
    • 对数器
    • 二分法查找
    • 方法参数传递是值还是引用
    • 链表
    • 位图
    • 位运算实现四则运算
    • 二叉树
    • 排序
    • 时间复杂度
    • 队列和栈
    • 递归
    • 堆(优先级队列)
    • 贪心
    • 并查集
    • 图
    • 从暴力递归到动态规划
    • 滑动窗口
      • 滑动窗口最大值
        • 暴力枚举
        • 滑动窗口
      • 达标子数组
        • 暴力枚举
        • 滑动窗口
      • 加油站问题
      • 最少货币问题
    • 单调栈
    • 线段树
  • 数据结构与算法

  • 计算机组成原理

  • 操作系统

  • 408
  • 数据结构
Iekr
2022-06-05
目录

滑动窗口

# 滑动窗口

认为规定好的一些运动轨迹 一开始左边界,右边界在数组的左侧,没有包住任何数 规定:

  1. 可以在任何时刻,让 R++, R 往右动,意味着一个数会从窗口的右侧进入窗口 除非到了终止位置,R 不要再往右了
  2. 可以在任何时刻,让 L++, L 往右动,意味着某一个数会从窗口的左侧出窗口

遵循一个原则: 左边界不要跑到右边界的再右侧去

可以动态的让窗口动态的从右侧进去,从左侧出去

img

我在每一个时刻都会维持一个窗口,想知道在一个窗口形成的状况下,窗口内的最大值是多少? 怎么得到窗口内的最大值 每次形成窗口的确可以遍历得到最大值,但是复杂度比较高 有没有非常好的办法,迅速的得到最大值?

双端队列

准备好一双端的队列 任何一个状态下,窗口内最大值的更新结构 准备一个数组 [6,4,2,3,5,7,0,5] 准备好一个双堆队列:可以在头部尾部进出,O (1)

最大值的更新结构: 从头部到尾部,从大到小

img

当 R 右移,一个数字进窗口 L..R 包住了 6 这个数,从尾部加入双端队列,双端队列是空的,6 直接进去

双端队列头部所代表的值就是此时最大值

img

R++,4 进入窗口,从尾部进入

从头部到尾部由大到小,4 在后面,没有改变由大到小这个事实

img

继续 R++ 直到来到 3 的位置 改变了由大到小的事实,不能直接进,怎么办? 从尾部弹出,2 位置的 2 弹出,扔出去的数再也不找回了 3 位置的 3 落到 4 后面

img

img

如有重复值 不保留旧的 只保留最新的

img

img

双端队列的根本含义 假设此时我如果让窗口依次缩小的话,哪些位置的数会依次成为窗口内的最大值

窗口的此时的最大值是 5

img

使 L 指针 ++ 缩窗口,轮到 1 位置的 3 成为最大值 增加窗口 2 位置的 0 过来 0 位置的 5, 1 位置的 3, 2 位置的 0 依次成为最大值

想让 L 往右,双端队列怎么更新 看头部的下标有没有过期 双端队列头部下标的数值就是窗口内的最大值

img

所以如果你 L 位置跟 R 位置会划过整数组中,所有的数 每一个位置的数,最多进一次,出一次 更新总代价 O (N), 查询很快 O (1), 均摊下来每一个数代价 O (1)

# 滑动窗口最大值

窗口内最大值或最小值更新结构的实现
假设一个固定大小为W的窗口,依次划过arr,
返回每一次滑出状况的最大值
例如,arr = [4,3,5,4,3,3,6,7], W = 3
返回:[5,5,5,4,6,7]
1
2
3
4
5

# 暴力枚举

	// 暴力方法
	public static int[] right(int[] arr, int w) {
		if (arr == null || w < 1 || arr.length < w) {
			return null;
		}
		int n = arr.length;
		int[] res = new int[n - w + 1];
		int index = 0;
		// 左指针
		int l = 0;
		// 右指针
		int r = w - 1;
		while (r < n) {
			int max = arr[l];
			// 左指针移动w个 求区间最大值
			for (int i = l + 1; i <= r; i++) {
				max = Math.max(max, arr[i]);
			}
			// 保存区间最大值结果
			res[index++] = max;
			// 左右指针滑动
			l++;
			r++;
		}
		return res;
	}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 滑动窗口

	public static int[] getMaxWindow(int[] arr,int w) {
		if (arr == null || w < 1 || arr.length < w) {
			return null;
		}
		//双端队列
		LinkedList<Integer> queue = new LinkedList<>();
		int[] res = new int[arr.length-w+1];
		int index = 0;
		for (int r = 0; r < arr.length; r++) {
			//队列不为空 并且当前队列未的数值 小于或等于 arr[r]
			while (!queue.isEmpty() && arr[queue.peekLast()] <= arr[r]) {
				//移除队列
				queue.pollLast();
			}
			//队列存储的为下标
			queue.add(r);
			//查看当前队头最大值的下标 是否超出当前窗口的范围内
			if (queue.peekFirst() == r -w) {
				//超出当前窗口范围 移除当前队列最大值
				queue.pollFirst();
			}
			//当前窗口已经形成 记录每个窗口的最大值
			if(r >= w-1) {
				res[index++] = arr[queue.peekFirst()];
			}
		}
		return res;
		
		
	}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

# 达标子数组

给定一个整型数组arr,和一个整数num
某个arr中的子数组sub,如果想达标,必须满足:sub中最大值 – sub中最小值 <= num,
返回arr中达标子数组的数量
1
2
3

# 暴力枚举

	// 暴力解法
	public static int right(int[] arr, int sum) {
		if (arr == null || arr.length == 0 || sum < 0) {
			return 0;
		}
		int n = arr.length;
		int count = 0;
		for (int l = 0; l < n; l++) {
			for (int r = l; r < n; r++) {
				int max = arr[l];
				int min = arr[l];
				// 从l..r窗口范围内遍历查找 最小值和最大值
				for (int i = l + 1; i <= r; i++) {
					max = Math.max(max, arr[i]);
					min = Math.min(min, arr[i]);
				}
				// 最大最小值 <= sum 则 count++
				if (max - min <= sum) {
					count++;
				}
			}
		}
		return count;

	}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 滑动窗口

假设某一个子数组从 L ... R 范围已经达标,则内部所有子数组一定也都达标

小范围的最大,最小值:最大值变小,最小值变大,所以一定达标

img

子数组 最大值变小,最小值变大,一定都达标

img

img

如果 L..R 不达标,则 L 往左扩,R 往右扩出来的新的子数组一定也都不达标 范围扩大,更加悬殊

准备窗口内最大值的更新结构 qmax 和最小值的两个更新结构 qmin 假设窗口从 0 开始 如果达标,窗口就继续扩展 扩到一旦不达标,停止扩展

一个范围 L...R 如果达标, 它内部所有子数组都达标 如果 L 到 R 已经不达标, R 再往右扩, 都不达标 让零开始看看扩到什么时候是初次不达标的时候

img

缩窗口,L 来到 1 位置,看 R 能不能往右扩 必须以 1 位置开头的字数组达标数量是多少个?整个过程中,整体代价 O (N) L,R 位置都不回退,窗口内两个最大值, 最小值更新结构,一共只不过 N 个数

	public static int num(int[] arr, int sum) {
		if (arr == null || arr.length == 0 || sum < 0) {
			return 0;
		}
		int n = arr.length;
		int count = 0;
		LinkedList<Integer> maxWindow = new LinkedList<>();
		LinkedList<Integer> minWindow = new LinkedList<>();
		int r = 0;
		for (int l = 0; l < n; l++) {
			while (r < n) {
				//max队列不为空 并且当前队列尾元素 小于或等于 arr[r]
				while (!maxWindow.isEmpty() && arr[maxWindow.peekLast()] <= arr[r]) {
					maxWindow.pollLast();
				}
				//加入当前下标
				maxWindow.addLast(r);
				//min队列不为空 并且当前队列尾元素 大于或等于 arr[r]
				while (!minWindow.isEmpty() && arr[minWindow.peekLast()] >= arr[r]) {
					minWindow.pollLast();
				}
				//加入当前下标
				minWindow.addLast(r);
				if(arr[maxWindow.peekFirst()] - arr[minWindow.peekFirst()] > sum) {
					//当前窗口最大最小值之差 大于sum 直接结束当前窗口的r指针增加
					break;
				} else {
					//否则r++ 继续扩大窗口大小
					r++;
				}

			}
			//当前窗口 一共有 r-l个达标子数组
			count += r- l;
			//接下来l要进行滑动
			//查看max窗口的最大值是否是l 如是l直接移除
			if(maxWindow.peekFirst() ==l) {
				maxWindow.pollFirst();
			}
			if(minWindow.peekFirst() == l) {
				minWindow.pollFirst();
			}
			
		}
		return count;
	}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

# 加油站问题

加油站的良好出发点问题
N个加油站组成一个环形,给定两个长度都是N的非负数组 gas和cost(N>1),gas[i]代表 第i个加油站存的油可以跑多少千米,cost[i]代表第i个加油站到环中下一个加油站相隔 多少千米。 假设你有一辆油箱足够大的车,初始时车里没有油。如果车从第i个加油站出发,最终 可以回到这个加油站,那么第i个加油站就算良好出发点,否则就不算。 请返回长度为N的boolean型数组res,res[i]代表第 i 个加油站是不是良好出发点。
1
2

# 最少货币问题

动态规划中利用窗口内最大值或最小值更新结构做优化(难)
arr是货币数组,其中的值都是正数。再给定一个正数aim。
每个值都认为是一张货币,
返回组成aim的最少货币数
注意:因为是求最少货币数,所以每一张货币认为是相同或者不同就不重要了
1
2
3
4
5
编辑 (opens new window)
上次更新: 2023/12/06, 01:31:48
从暴力递归到动态规划
单调栈

← 从暴力递归到动态规划 单调栈→

最近更新
01
k8s
06-06
02
进程与线程
03-04
03
计算机操作系统概述
02-26
更多文章>
Theme by Vdoing | Copyright © 2022-2025 Iekr | Blog
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式