滑动窗口算法

一.什么是滑动窗口

滑动窗口(Sliding Window)是一种在数组、列表、字符串上常用的算法技巧,用于高效地处理固定长度或可变长度的连续子序列(子数组/子串)的问题。

核心思想:

维护"一个窗口"(即一段连续的区间),通过移动窗口的左右边界,避免重复计算,从而优化时间复杂度。

二.经典应用场景

1.固定窗口大小(最常见)

问题:给定数组,求每个长度为k的连续子数组的最大值(或最小值,和等)

python 复制代码
for i in range(n-k+1): 
# 因为每组是k个元素,最后一组的最后元素索引是i+k(i,i+1,i+2,...,i+k-1,刚好k个元素),
# i+k作为切片的末索引,因为切片中是切到末索引前一个索引(即i+k-1)
# 所以i+k-1 = n-1 则 i要取到n-k,则range()中的参数应该为n-k+1
    max_val = max(arr[i:i+k])

如:

python 复制代码
arr = [1,2,3,4,5,6,7,8,9,10]
n = len(arr)
k = 3
for i in range(n-k+1):
    # print(i,end=' ')
    max_val = max(arr[i:i+k])
    print(max_val)
"""
输出:
3
4
5
6
7
8
9
10
"""

这个模板属于暴力解法,性能比较低效

可以使用单调队列来加以优化

先来复习一下队列的知识

队列是一种常用的数据结构,特点是元素"先进先出";只能在队尾插入,队头删除;可以用排队买票来类比

队列的实现方式可以手写 ,也可以使用Python的内置库:collections.queue (collections库提供了许多增强版的数据容器类型,主要用于代替或扩展内置的list、dict、tuple、set等,让代码更简洁,高效,语义清晰)、queue.Queue(线程安全,用于多线程,普通算法题不要用这个,因为这个慢且不支持索引/切片)

双端队列:两端都可以进行插入和删除的队列

collections.deque,所有操作的时间复杂度都是O(1),(底层用双向链表和循环数组实现)

python 复制代码
from collections import deque
def sliding_window_max(arr,k):
    """返回arr中每个长度为k的滑动窗口的最大值列表"""
    dq = deque() # 队列用来存储索引
    res = []
    for i in range(len(arr)):
        # 移除超出窗口(窗口长度k)的索引
        if dq and dq[0] == i-k: # 如何判断刚好是长度为k的窗口?当循环到索引i时,dq中要只需有i-k+1,...,i-2,i-1,i这k个元素就行,但如果有i-k,就要移除
            # 可以改成dq[0] < i-k+1吗?可以(小于窗口左边界i-k+1就要删除),但每次i只增加1,最多只有1个索引会过期,原写法效率会高一点(只判断一次)
            dq.popleft() # 从队列的左端移除
        # 维护单调递减队列
        while dq and arr[dq[-1]] < arr[i]: # 新的元素要小于等于dq末尾索引对应的元素才能入队
            dq.pop()
        dq.append(i)
        if i >= k-1: # 如果遍历到i大于等于k-1(为什么是k-1,因为range从0开始,0,1,2,...,k-1,刚好k个元素)时,说明窗口长度已经达标了
            res.append(arr[dq[0]]) # 由于维护的是递减队列,队首就是最大的元素的索引
    return res

res = sliding_window_max([1,2,3,4,5,4,7,11,9,10],3)
print(res) # [3, 4, 5, 5, 7, 11, 11, 11]

蓝桥云课3521题:

python 复制代码
n, m, a, b = map(int, input().split())
matrix = []
for _ in range(n):
    matrix.append(list(
        map(int, input().split())
        ))

res = 0
start_row, end_row = 0, a
start_col, end_col = 0, b
while end_row <= n: # 这一层while循环要循环多少次?end_row最开始是a,最后取值为n,从a到n一共有n-a+1个数,那么这层while循环就要循环n-a+1次
    temp = matrix[start_row:end_row]
    start_row += 1
    end_row += 1
    while end_col <= m: # 这层while循环要循环m-b+1次
        submatrix = [sub[start_col:end_col] for sub in temp]
        start_col += 1
        end_col += 1
        # print(submatrix)
        min_val = min(min(row) for row in submatrix) # 求子矩阵的最小值:对每一行的b个元素求最小值要比较b-1次,有a行那么就有a个最小值,那么还要比较a-1次。共要比较(a-1)*(b-1)次
        max_val = max(max(row) for row in submatrix) # 求子矩阵的最大值:同理要比较(a-1)*(b-1)次
                                                     # 因此
        value = min_val * max_val
        res += value
    start_col, end_col = 0, b # 重置
print(res % 998244353 )
### 这个算法的时间复杂度为:O(mnab)?还是O((m-b+1)*(n-a+1)*(a-1)*(b-1))
"""
现代CPU每秒可执行越10^9次操作
综合多种因素,可以认为Python每秒可以执行10^7次操作
"""

只通过了60%的测试用例,超时了。

python 复制代码
from collections import deque

def sliding_window_max(arr, k):
    """返回arr中每个长度为k的滑动窗口的最大值列表"""
    dq = deque()
    res = []
    for i in range(len(arr)):
        if dq and dq[0] == i-k:
            dq.popleft()
        while dq and arr[dq[-1]] <= arr[i]:
            dq.pop()
        dq.append(i)
        if i >= k-1:
            res.append(arr[dq[0]])

    return res

def sliding_window_min(arr, k):
    """返回arr中每个长度为k的滑动窗口的最小值列表"""
    dq = deque()
    res = []
    for i in range(len(arr)):
        if dq and dq[0] == i-k:
            dq.popleft()
        # 维护一个单调递增队列
        while dq and arr[dq[-1]] >= arr[i]:
            dq.pop()
        dq.append(i)
        if i >= k-1:
            res.append(arr[dq[0]])
    
    return res

n, m, a, b = map(int, input().split())
matrix = []
for _ in range(n):
    matrix.append(list(
        map(int, input().split())
        ))
    
row_mins, row_maxs = [], []
for i in range(n):
    # 对每一行的滑动窗口求最小值,最大值
    row_mins.append(sliding_window_min(matrix[i], b)) # n行m-b+1列(从b-1到m-1,有m-1-(b-1)+1=m-b+1个)
    row_maxs.append(sliding_window_max(matrix[i], b)) # n行m-b+1列

# print(row_mins)
# print(row_maxs)

total = 0
MOD = 998244353
cols = len(row_mins[0]) # m-b+1
for j in range(cols):
    # 提取第j列的所有行的窗口的最小值,最大值
    col_mins_val = [row_mins[i][j] for i in range(n)] # n行n列
    col_maxs_val = [row_maxs[i][j] for i in range(n)]

    # 对列方向做高度为a的滑动窗口
    win_mins = sliding_window_min(col_mins_val, a)
    win_maxs = sliding_window_max(col_maxs_val, a) # n行n-a+1列

    for idx in range(len(win_mins)):
        total = (total + win_mins[idx]*win_maxs[idx]) % MOD

print(total)
相关推荐
im_AMBER2 小时前
Leetcode 103 反转链表 II
数据结构·c++·笔记·学习·算法·leetcode
Ka1Yan3 小时前
[链表] - 代码随想录 24. 两两交换链表中的节点
数据结构·链表
Python_Study20253 小时前
制造业企业如何构建高效数据采集系统:从挑战到实践
大数据·网络·数据结构·人工智能·架构
lixinnnn.3 小时前
优先级队列:最小函数值
数据结构·算法
Xの哲學3 小时前
Linux Worklet 深入剖析: 轻量级延迟执行机制
linux·服务器·网络·数据结构·算法
666HZ6663 小时前
数据结构2.1 线性表习题
c语言·数据结构·算法
yangminlei3 小时前
MySQL玩转数据可视化
数据结构·sql·oracle
CodeByV4 小时前
【算法题】链表
数据结构·算法
wen__xvn4 小时前
代码随想录算法训练营DAY15第六章 二叉树part03
数据结构·算法·leetcode