最小矩阵宽度

一、题目描述

给定一个矩阵,包含N*M个整数,和一个包含K个整数的数组现在要求在这个矩阵中找一个宽度最小的子矩阵,要求子矩阵包含数组中所有的整数。

二、输入输出描述

输入描述

  • 第一行:两个正整数 N(行数)、M(列数);
  • 接下来 N 行:每行 M 个整数,表示矩阵内容;
  • 下一行:正整数 K(目标数组长度);
  • 最后一行:K 个整数,表示目标数组(可能有重复)。

输出描述

  • 一个整数,表示满足要求子矩阵的最小宽度,若找不到,输出-1。

三、示例

|----|---------------------------------|
| 输入 | 2 5 1 2 2 3 1 2 3 2 3 2 3 1 2 3 |
| 输出 | 2 |
| 说明 | 矩阵第0、3列包含了1、2、3,矩阵第3、4列包含了1、2、3 |

|----|---------------------------------|
| 输入 | 2 5 1 2 2 3 1 1 3 2 3 4 3 1 1 4 |
| 输出 | 5 |
| 说明 | 矩阵第1,2,3,4,5列包含了1,1,4 |

四、解题思路

  1. 核心思想
  • 维度降维:将 "二维矩阵的连续列子矩阵" 问题转化为 "一维列窗口" 问题 ------ 先统计每列的数字频次,把 "列" 作为基本单元,窗口的宽度就是子矩阵的宽度;
  • 滑动窗口优化:用滑动窗口(双指针)遍历列维度,统计窗口内数字的总频次,当窗口内频次满足目标数组的所有要求时,收缩左边界找最小宽度,时间复杂度从暴力的 O (M²) 优化到 O (M)。
  1. 问题本质分析

该问题是带频次约束的最小窗口子数组问题的二维扩展,核心特征:

  • 约束:子矩阵是 "连续列" 组成的(行全选,列连续),且子矩阵中目标数组的每个数字的出现次数≥目标数组中的次数;
  • 优化目标:子矩阵的宽度(列数)最小;
  • 关键转化:矩阵的 "连续列子矩阵" 等价于 "列的连续窗口",每列的数字频次是窗口的基本单元,只需统计窗口内的总频次是否满足目标;
  • 核心难点:需要同时满足 "数字种类" 和 "每个数字的频次" 要求。
  1. 核心逻辑
  • 列频次预处理:统计每列中每个数字的出现次数,把二维矩阵转化为 "列频次数组",降低后续计算复杂度;
  • 目标频次构建:统计目标数组中每个数字的需要次数,明确 "满足条件" 的标准;
  • 滑动窗口遍历列
    1. 右边界右移:将当前列的频次加入窗口,更新窗口内总频次,统计 "满足频次要求的数字种类数";
    2. 满足条件时收缩左边界:尝试缩小窗口宽度,同时更新最小宽度;移除左列频次时,若某数字的频次不再满足目标,更新 "满足种类数";
  • 结果判断:遍历完成后,若找到有效窗口则返回最小宽度,否则返回 - 1。
  1. 步骤拆解

  2. 输入处理:读取矩阵大小、矩阵内容、目标数组;

  3. 列频次预处理 :遍历每一列,统计该列中每个数字的出现次数,存储到colCount数组;

  4. 目标频次构建 :遍历目标数组,统计每个数字需要的总次数,存储到targetMap

  5. 滑动窗口遍历列

    • 初始化:左指针left=0、匹配种类数match=0、当前窗口频次currMap、最小宽度minWidth=极大值
    • 右指针遍历每一列:
      1. 加入当前列的频次到currMap,若某数字频次刚满足目标,match+1
      2. match=目标种类数(窗口满足条件):
        • 计算当前窗口宽度,更新minWidth
        • 移除左列的频次,若某数字频次不再满足目标,match-1
        • 左指针右移,收缩窗口;
  6. 结果输出 :若minWidth仍为极大值,返回 - 1;否则返回minWidth

五、代码实现

java 复制代码
import java.util.*;

public class MinWidthSubmatrix {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        
        // 1. 读取矩阵大小 N(行) M(列)
        int N = scanner.nextInt();
        int M = scanner.nextInt();
        scanner.nextLine(); // 换行符
        
        // 2. 读取矩阵内容
        int[][] matrix = new int[N][M];
        for (int i = 0; i < N; i++) {
            for (int j = 0; j < M; j++) {
                matrix[i][j] = scanner.nextInt();
            }
            scanner.nextLine();
        }
        
        // 3. 读取目标数组参数
        int K = scanner.nextInt();
        scanner.nextLine();
        int[] targetArr = new int[K];
        for (int i = 0; i < K; i++) {
            targetArr[i] = scanner.nextInt();
        }
        
        // 4. 计算最小宽度
        int result = findMinWidth(matrix, N, M, targetArr);
        System.out.println(result);
        
        scanner.close();
    }
    
    /**
     * 核心方法:寻找包含目标数组所有数字的最小宽度子矩阵
     * @param matrix 输入矩阵
     * @param N 矩阵行数
     * @param M 矩阵列数
     * @param targetArr 目标数组
     * @return 最小宽度,无则返回-1
     */
    private static int findMinWidth(int[][] matrix, int N, int M, int[] targetArr) {
        // 步骤1:统计每列的数字频次 - colCount[j][num] = 第j列num的出现次数
        Map<Integer, Integer>[] colCount = new HashMap[M];
        for (int j = 0; j < M; j++) {
            colCount[j] = new HashMap<>();
            for (int i = 0; i < N; i++) {
                int num = matrix[i][j];
                colCount[j].put(num, colCount[j].getOrDefault(num, 0) + 1);
            }
        }
        
        // 步骤2:构建目标频次映射(考虑重复)
        Map<Integer, Integer> targetMap = new HashMap<>();
        for (int num : targetArr) {
            targetMap.put(num, targetMap.getOrDefault(num, 0) + 1);
        }
        int targetType = targetMap.size(); // 目标数字的种类数
        
        // 步骤3:滑动窗口找最小列窗口
        int left = 0;
        int match = 0; // 已满足频次要求的数字种类数
        Map<Integer, Integer> currMap = new HashMap<>();
        int minWidth = Integer.MAX_VALUE;
        
        for (int right = 0; right < M; right++) {
            // 加入当前列的频次到窗口
            Map<Integer, Integer> currCol = colCount[right];
            for (Map.Entry<Integer, Integer> entry : currCol.entrySet()) {
                int num = entry.getKey();
                int cnt = entry.getValue();
                // 仅处理目标数组中的数字
                if (targetMap.containsKey(num)) {
                    currMap.put(num, currMap.getOrDefault(num, 0) + cnt);
                    // 若当前数字的频次刚满足目标,匹配数+1
                    if (currMap.get(num).equals(targetMap.get(num))) {
                        match++;
                    }
                }
            }
            
            // 尝试收缩左指针,寻找最小窗口
            while (match == targetType) {
                // 更新最小宽度
                int currWidth = right - left + 1;
                if (currWidth < minWidth) {
                    minWidth = currWidth;
                }
                
                // 移除左列的频次
                Map<Integer, Integer> leftCol = colCount[left];
                for (Map.Entry<Integer, Integer> entry : leftCol.entrySet()) {
                    int num = entry.getKey();
                    int cnt = entry.getValue();
                    if (targetMap.containsKey(num)) {
                        int currCnt = currMap.get(num);
                        // 若当前数字的频次刚好满足,移除后不再满足,匹配数-1
                        if (currCnt == targetMap.get(num)) {
                            match--;
                        }
                        currMap.put(num, currCnt - cnt);
                        // 频次为0时移除,避免冗余
                        if (currMap.get(num) == 0) {
                            currMap.remove(num);
                        }
                    }
                }
                left++;
            }
        }
        
        // 步骤4:结果处理
        return minWidth == Integer.MAX_VALUE ? -1 : minWidth;
    }
}
相关推荐
Σίσυφος19002 小时前
ICP 为啥会陷入到局部?为何point to Plane 比point to Point 收敛更快?
算法
难忘经典2 小时前
Java进阶(ElasticSearch的安装与使用)
java·elasticsearch·jenkins
liuyao_xianhui2 小时前
动态规划_最长递增子序列_C++
java·开发语言·数据结构·c++·算法·链表·动态规划
不想看见4042 小时前
Find All Numbers Disappeared in an Array数组--力扣101算法题解笔记
笔记·算法·leetcode
月明长歌2 小时前
【码道初阶-Hot100】LeetCode 438 + 567 对照详解:一套滑动窗口模板,彻底讲透“固定长度窗口 + 计数数组 + count维护”
算法·leetcode·滑动窗口
luoganttcc2 小时前
华为 昇腾 架构怎么 解决这个 NCCL 通信问题
华为·架构
旖-旎2 小时前
二分查找(搜索插入位置)(3)
c++·算法·二分查找·力扣·双指针
XW01059992 小时前
5-11字典合并
数据结构·python·算法
wunianor2 小时前
[算法]2026年3月14日米哈游校招算法笔试题题解
算法