最小子数组、最大子矩阵,快速了解滑动窗口

从暴力解法到滑动窗口。

从一道基础算法题快速了解滑动窗口,再一道竞赛题快速加深对滑动窗口的理解。

 长度最小的子数组

链接:https://leetcode.cn/problems/minimum-size-subarray-sum/description/

暴力解法:直接两层循环遍历出所有区间,取最小长度。结果会是超时的。

class Solution {    
    public int minSubArrayLen(int target, int[] nums) { 
        int res = nums.length + 1;  // 当然Integer.MAX_VALUE也可以,return判断改一下即可
        for (int i = 0,sum = 0; i < nums.length; i++) {
            for (int j = i; j < nums.length; j++) {
                sum += nums[j];
                if (sum >= target) {
                    res = Math.min(res, j - i + 1);
                    break; 
                }
             }
             sum = 0;
         }
         return (res == nums.length + 1) ? 0 : res;
    }
}
// 暴力-逻辑-易 
// 时间复杂度:$O(n^2)$ 空间复杂度:$O(1)$

滑动窗口解法:

滑动窗口,两层循环变一层循环,那么外循环一般都会是对应终止位置。
先遍历外层终止位置:
        当子数组中存在比target大或等的数,那么就开始移动起始位置,同时更新最小长度,直                到不存在这样的target(可以做个优化,如果最小长度为1,break)

class Solution {    
    public int minSubArrayLen(int target, int[] nums) {
        int res = nums.length + 1;   // 当然Integer.MAX_VALUE也可以
        for (int i = 0, sum = 0, index = 0; i < nums.length; i++) {
            sum += nums[i];
            while (sum >= target) {
                res = Math.min(res, i - index + 1);   // 可以优化:当res==1时,直接return
                sum -= nums[index++];   // 用哪个index,就减去哪一个
            }
        }
        return (res == nums.length + 1) ? 0 : res;
    }
}
// 双指针——优
// 时间复杂度:$O(n)$ 空间复杂度:$O(1)$

最大子矩阵

时间限制: 1.0s 内存限制: 512.0MB 本题总分:15 分
【问题描述】
小明有一个大小为 N × M 的矩阵,可以理解为一个 N 行 M 列的二维数组。
我们定义一个矩阵 m 的稳定度 f(m) 为 f(m) = max(m) − min(m),其中 max(m)
表示矩阵 m 中的最大值,min(m) 表示矩阵 m 中的最小值。现在小明想要从这
个矩阵中找到一个稳定度不大于 limit 的子矩阵,同时他还希望这个子矩阵的面
积越大越好(面积可以理解为矩阵中元素个数)。
子矩阵定义如下:从原矩阵中选择一组连续的行和一组连续的列,这些行
列交点上的元素组成的矩阵即为一个子矩阵。
【输入格式】
第一行输入两个整数 N,M,表示矩阵的大小。
接下来 N 行,每行输入 M 个整数,表示这个矩阵。
最后一行输入一个整数 limit,表示限制。
【输出格式】
输出一个整数,分别表示小明选择的子矩阵的最大面积。
【样例输入】

3 4
2 0 7 9
0 6 9 7
8 4 6 4
8

【样例输出】

6

【样例说明】
满足稳定度不大于 8 的且面积最大的子矩阵总共有三个,面积都是6(粗体表示子矩阵元素):
2 0 7 9
0 6 9 7
8 4 6 4


2 0 7 9
0 6 9 7
8 4 6 4


2 0 7 9
0 6 9 7
8 4 6 4

【滑动窗口解法】:

本题还自行使用单独队列自行维护,用单调队列来实现滑动窗口算法。

在入值时,把矩阵每列的行位置(1-n)对应最大值、最小值维护好,然后给予固定范围比较时,就相当于不同列之间的一维数组做比较,根据窗口的移动,维护单独队列,以查看是否达到限制,若没有则TRUE,有则到最后一列时返回FALSE。

import java.util.*;

public class Main {
  static int[][][] max; 
  static int[][][] min; 
  static int n, m, limit, res; 
  public static void main(String[] args) { 
    Scanner sc = new Scanner(System.in);
    n = sc.nextInt(); 
    m = sc.nextInt();
    max = new int[m+1][n+1][n+1]; 
    min = new int[m+1][n+1][n+1]; 
    for (int i = 1; i <= n; i++) { 
      for (int j = 1; j <= m; j++) { 
        max[j][i][i] = min[j][i][i] = sc.nextInt();
      } 
    } 
/**

max[1][1][1] = min[1][1][1] = 2   max[2][1][1] = min[2][1][1] = 0   max[3][1][1] = min[3][1][1] = 7  ...
max[1][2][2] = min[1][2][2] = 0   ...

*/

    limit = sc.nextInt();
    // max[k][i][j]表示第k列中[i,j]之间的最大值 
    for (int k = 1; k <= m; k++) {  // 遍历列
      for (int i = 1; i < n; i++) {  // 遍历行
        for (int j = i + 1; j <= n; j++) {  // 遍历行后
          max[k][i][j] = Math.max(max[k][i][j - 1], max[k][j][j]);  // f(j) = f(j-1)  / f(j) 
          min[k][i][j] = Math.min(min[k][i][j - 1], min[k][j][j]); 
        } 
      } 
    } 


    for (int x1 = 1; x1 <= n; x1++) {  // 遍历行
      for (int x2 = x1; x2 <= n; x2++) {  // 遍历行和行后(因为有为1)
        int l = 1, r = m;  // 为什么这里不直接遍历m到1或1到m,因为用二分能加快查速
        while (l < r) { 
          int mid = l + r + 1 >> 1;  // 偏右的位置(这样不会出现mid重复的情况,因为mid是给left的)
          if (check(x1, x2, mid)) {
            l = mid; 
          } else {
            r = mid - 1; 
          }
        } 
        if (check(x1,x2,r)) {
          res = Math.max(res, (x2 - x1 + 1) * r); 
        }
      } 
    } 
    System.out.println(res); 
  }
  /* 
  单调队列 + 滑动窗口
  检查x1 x2范围内是否有连续k列满足条件
  x1 x2为行范围,k为列的最大长度
  */
  private static boolean check(int x1, int x2, int k) {
    Deque<Integer> MAX = new ArrayDeque<Integer>();
    Deque<Integer> MIN = new ArrayDeque<Integer>();  // 塞入队列的是列元素
    for (int i = 1; i <= m; i++) {  // 遍历所有列
        if (!MAX.isEmpty() && i - MAX.peekFirst() + 1 > k) {  // 如果超过列最大长度,去除最前面的(发生前k列不满足的情况)
          MAX.pollFirst();  
        }
        while (!MAX.isEmpty() && max[MAX.peekLast()][x1][x2] < max[i][x1][x2]) {
          MAX.pollLast();  // MAX只会依次放入大值,维持一个单调递增,队列尾最大的情况
        }
        MAX.offerLast(i);  // 入队尾(会发现,第i列在队列中的位置,不会超过i-1,所以保证了前k列不满足,抛出队列头后还能继续判断)
        if (!MIN.isEmpty() && i - MIN.peekFirst() + 1 > k) {  // 如果超过列最大长度,去除最前面的
          MIN.pollFirst();
        }
        while (!MIN.isEmpty() && min[MIN.peekLast()][x1][x2] > min[i][x1][x2]) {
          MIN.pollLast();  // MIN只会依次放入小值,维持一个单调递减,队列尾最小的情况
        }
        MIN.offerLast(i);  // 入队尾
        // 当i大于最大长度时,若最大值与最小值之差在limit内,则返回true,否则直到i到m返回false
        if (i >= k && max[MAX.peekFirst()][x1][x2] - min[MIN.peekFirst()][x1][x2] <= limit) {
          return true;
        }
    }
    return false;
  }
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值