1091 理想的正方形(单调队列优化)

1. 问题描述:

有一个 a×b 的整数组成的矩阵,现请你从中找出一个 n×n 的正方形区域,使得该区域所有数中的最大值和最小值的差最小。

输入格式

第一行为三个整数,分别表示 a,b,n 的值;第二行至第 a+1 行每行为 b 个非负整数,表示矩阵中相应位置上的数。

输出格式

输出仅一个整数,为 a×b 矩阵中所有“n×n 正方形区域中的最大整数和最小整数的差值”的最小值。

数据范围

2 ≤ a,b ≤ 1000,
n ≤ a,n ≤ b,n ≤ 100,
矩阵中的所有数都不超过 10 ^ 9。

输入样例:

5 4 2
1 2 5 6
0 17 16 0
16 17 2 1
2 10 2 1
1 2 2 2

输出样例:

1
来源:https://www.acwing.com/problem/content/description/1093/

2. 思路分析:

这道题目是在二维平面内求解长度为k的正方形的最值,最容易想到的方法是求解每一行所有长度为k的区间最值,然后在列的方向上再求解一遍最值得到的就是以当前列为右端点的长度为k的区间最值,而在行方向上或者列方向上求解最值实际上是一维平面内滑动窗口求最值的问题,可以使用单调队列优化,我们可以求解出每一行与每一列的最值之后保存到对应的两个数组上即可(row_min保存行最小值,row_max保存行最大值),每一次求解以当前列的位置作为右端点的长度为k的正方形的最值,枚举每一列对应的所有正方形的最值即可得到所有长度为k的正方形的最值,在枚举的时候更新答案即可。

3. 代码如下:

下标从1开始:

from typing import List


class Solution:
    # 单调队列的过程, 求解每一行长度为k的最小值(求解最大值的时候也是类似的, 只需要修改一下判断的符号即可)
    def getMin(self, a: List[int], b: List[int], maxLen: int, k: int):
        q = [0] * (maxLen + 1)
        hh, tt = 0, -1
        for i in range(1, len(a)):
            # 因为一开始初始化的时候tt = -1所以这里需要判断一下hh是否小于等于tt
            if hh <= tt and q[hh] <= i - k: hh += 1
            while hh <= tt and a[i] <= a[q[tt]]: tt -= 1
            tt += 1
            q[tt] = i
            # 以当前位置i为右端点的长度为k的区间长度最小值为a[q[hh]]
            b[i] = a[q[hh]]
    
    # 单调队列的过程, 求解每一行长度为k的最大值
    def getMax(self, a: List[int], b: List[int], maxLen: int, k: int):
        q = [0] * (maxLen + 1)
        hh, tt = 0, -1
        for i in range(1, len(a)):
            if hh <= tt and q[hh] <= i - k: hh += 1
            while hh <= tt and a[i] >= a[q[tt]]: tt -= 1
            tt += 1
            q[tt] = i
            b[i] = a[q[hh]]

    def process(self):
        n, m, k = map(int, input().split())
        # 求解矩阵边长的最大值(因为后面是有单调队列求解最值的时候每一行和每一列都需要求解最值而且都是调用同一个方法所以需要先求解出矩阵最大的边长)
        maxLen = max(n, m)
        # 先声明第一行全为0这样下标可以从1开始
        w = [[0] * (m + 1)]
        for i in range(n):
            # 每一行前面加上一个0这样下标可以从1开始
            w.append([0] + list(map(int, input().split())))
        # 记录行的最小值和最大值
        row_min, row_max = [[0] * (m + 1) for i in range(n + 1)], [[0] * (m + 1) for i in range(n + 1)]
        for i in range(1, n + 1):
            self.getMin(w[i], row_min[i], maxLen, k)
            self.getMax(w[i], row_max[i], maxLen, k)
        # a, b, c为三个辅助列表, 每一次都是以当前列的所在长度为k的正方形的最大值与最小值的差值
        a, b, c = [0] * (maxLen + 1), [0] * (maxLen + 1), [0] * (maxLen + 1)
        res = 10 ** 9
        for i in range(k, m + 1):
            # 先将当前这一列的所有最小值先保存到a数组中
            for j in range(1, n + 1):
                a[j] = row_min[j][i]
            # 将求解的列最小值结果保存到b数组中
            self.getMin(a, b, maxLen, k)
            for j in range(1, n + 1):
                a[j] = row_max[j][i]
            # 将求解的列最大值结果保存到c数组中
            self.getMax(a, c, maxLen, k)
            # 枚举以当前所以列为右端点长度为k的正方形, 注意起点应该是k这样才能够构成长度为k的正方形
            for j in range(k, n + 1): res = min(res, c[j] - b[j])
        return res


if __name__ == "__main__":
    print(Solution().process())

下标从0开始:

from typing import List


class Solution:
    # 获得当前所有长度为k的最小值, 答案记录在b中
    def getMin(self, k: int, maxLen: int, a: List[int], b: List[int]):
        hh, tt = 0, -1
        q = [0] * maxLen
        for i in range(len(a)):
            if hh <= tt and q[hh] <= i - k: hh += 1
            while hh <= tt and a[i] <= a[q[tt]]: tt -= 1
            tt += 1
            q[tt] = i
            b[i] = a[q[hh]]
    
    # 获得当前所有长度为k的最大值, 答案记录在c中
    def getMax(self, k: int, maxLen: int, a: List[int], b: List[int]):
        hh, tt = 0, -1
        q = [0] * maxLen
        for i in range(len(a)):
            if hh <= tt and q[hh] <= i - k: hh += 1
            while hh <= tt and a[i] >= a[q[tt]]: tt -= 1
            tt += 1
            q[tt] = i
            b[i] = a[q[hh]]

    def process(self):
        # n * m的矩阵, 寻找k * k的正方形的最大整数与最小整数的差值
        n, m, k = map(int, input().split())
        w = list()
        # w存储的矩阵中的元素值
        for i in range(n):
            w.append(list(map(int, input().split())))
        maxLen = max(n, m) + 10
        row_min, row_max = [[0] * maxLen for i in range(maxLen)], [[0] * maxLen for i in range(maxLen)]
        # 求解每一行的长度为k的最大值与最小值
        for i in range(n):
            # 下标是从0开始的
            self.getMin(k, maxLen, w[i], row_min[i])
            self.getMax(k, maxLen, w[i], row_max[i])
        # a为辅助列表, 用来存储当前这一列的最值
        a, b, c = [0] * maxLen, [0] * maxLen, [0] * maxLen
        res = 10 ** 10
        # 从第k - 1列开始枚举(下标从0开始)
        for i in range(k - 1, m):
            for j in range(n):
                a[j] = row_min[j][i]
            self.getMin(k, maxLen, a, b)
            for j in range(n):
                a[j] = row_max[j][i]
            self.getMax(k, maxLen, a, c)
            # 枚举答案
            for j in range(k - 1, n):
                res = min(res, c[j] - b[j])
        return res


if __name__ == "__main__":
    print(Solution().process())
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值