小J的字母矩阵问题 | 豆包MarsCode AI刷题

83 阅读6分钟

1.问题描述

小R拿到了一个 nn 行 mm 列的小写字母矩阵,她想知道有多少个子矩阵满足以下条件:每个字母在这个子矩阵中最多出现一次。

问题源码

public class Main {
    public static int solution(int n, int m, String[] s) {
        // write code here
        return 0;
    }

    public static void main(String[] args) {
        System.out.println(solution(2, 3, new String[]{"aad", "abc"}) == 13);
        System.out.println(solution(3, 3, new String[]{"abc", "def", "ghi"}) == 36);
        System.out.println(solution(4, 4, new String[]{"abcd", "efgh", "ijkl", "mnop"}) == 100);
    }
}

2.问题解决源代码(java)

引用

import java.util.HashSet;
import java.util.Set;

主函数

计算满足条件的子矩阵数量。该程序遍历矩阵中的每个元素,作为 子矩阵的左上角;循环嵌套第三层,便利当前左上角右侧和下方的所有元素,作为子矩阵的右下角;循环嵌套第四层,检查当前子矩阵是否满足条件,如果满足条件,计数加一。

public static int solution(int n, int m, String[] s) {
        int count = 0;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                for (int k = i; k < n; k++) {
                    for (int l = j; l < m; l++) {
                        if (isValidSubmatrix(i, j, k, l, s)) {
                            count++;
                        }
                    }
                }
            }
        }
        return count;//返回满足条件的子矩阵总数
    }

辅助函数

检查子矩阵是否有效的辅助函数

private static boolean isValidSubmatrix(int top, int left, int bottom, int right, String[] s) {
        Set<Character> letters = new HashSet<>(); // 用于存储子矩阵中的字母
        // 遍历子矩阵中的每个元素
        for (int i = top; i <= bottom; i++) {
            for (int j = left; j <= right; j++) {
                char c = s[i].charAt(j); // 获取当前元素
                // 如果字母已经存在于集合中,说明子矩阵不满足条件
                if (letters.contains(c)) {
                    return false;
                }
                letters.add(c); // 将字母添加到集合中
            }
        }
        return true; // 如果所有字母都只出现一次,子矩阵满足条件
    }

3.程序UML类图及数据结构

UML类图

image.png

数据结构

1. 数组 (Array)
  • String[] s:这是一个二维字符数组,表示输入的矩阵。每个元素是一个字符串,代表矩阵的一行。
2. 哈希集合 (HashSet)
  • Set<Character> letters:这是一个哈希集合,用于存储子矩阵中的字符。哈希集合的特点是不允许重复元素,这使得检查字符是否唯一变得非常高效。

详细说明

数组 (Array)
  • String[] s:这是一个一维数组,但每个元素是一个字符串,实际上形成了一个二维字符数组。例如:
    String[] s = {"aad", "abc"};
    
    这个数组可以看作是一个 2x3 的矩阵: a a d a b c
哈希集合 (HashSet)
  • Set<Character> letters:这是一个哈希集合,用于存储子矩阵中的字符。哈希集合的特点是插入和查找操作的时间复杂度为 O(1),非常适合用于检查字符是否唯一。例如:
    Set<Character> letters = new HashSet<>();
    
    在遍历子矩阵的过程中,将每个字符添加到集合中:
    letters.add(c);
    
    如果某个字符已经存在于集合中,说明子矩阵中有重复字符,返回 false
    if (letters.contains(c)) {
        return false;
    }
    

数据结构的使用场景

  • 数组:用于存储输入的矩阵数据,方便按行和列访问每个字符。
  • 哈希集合:用于在常数时间内检查字符是否唯一,确保子矩阵中的每个字符只出现一次。
示例

假设输入矩阵为:

String[] s = {"aad", "abc"};

遍历子矩阵的过程中,哈希集合 letters 的使用示例如下:

  1. 初始状态:letters = {}
  2. 处理子矩阵 s[0][0]s[0][0](即字符 'a'):
    • letters.add('a') -> letters = {'a'}
  3. 处理子矩阵 s[0][0]s[0][1](即字符 'a', 'a'):
    • letters.add('a') -> letters = {'a'}
    • letters.contains('a') 返回 true,所以子矩阵不满足条件。

通过这种方式,哈希集合有效地帮助我们检查子矩阵中的字符是否唯一。

4.程序算法分析与总结

暴力枚举算法

算法分析

该程序的主要目的是计算给定矩阵中满足特定条件的子矩阵的数量。具体来说,这些子矩阵中的每个字符都必须是唯一的,即不能有重复的字符。程序使用了四重循环来遍历所有可能的子矩阵,并使用一个辅助函数来检查每个子矩阵是否满足条件。

时间复杂度分析
  1. 四重循环

    • 外层两重循环遍历所有可能的左上角坐标 (i, j)
    • 内层两重循环遍历所有可能的右下角坐标 (k, l)
    • 因此,四重循环的总时间复杂度为 O(n2m2)O(n^2 \cdot m^2),其中 nn 是矩阵的行数,mm 是矩阵的列数。
  2. 辅助函数 isValidSubmatrix

    • 该函数遍历子矩阵中的每个字符,并使用哈希集合来检查字符是否唯一。
    • 子矩阵的大小范围是从 1×11 \times 1n×mn \times m,因此该函数在最坏情况下会被调用 O(n2m2)O(n^2 \cdot m^2) 次。
    • 每次调用 isValidSubmatrix 函数的时间复杂度为 O((ki+1)(lj+1))O((k-i+1) \cdot (l-j+1)),即子矩阵的大小。

综合考虑,程序的总时间复杂度为 O(n2m2(ki+1)(lj+1))O(n^2 \cdot m^2 \cdot (k-i+1) \cdot (l-j+1))。在最坏情况下,子矩阵的大小可以达到 n×mn \times m,因此总时间复杂度可以简化为 O(n4m4)O(n^4 \cdot m^4)

空间复杂度分析
  1. 哈希集合 letters

    • 在每次调用 isValidSubmatrix 函数时,哈希集合最多存储子矩阵中的所有字符。
    • 因此,空间复杂度在最坏情况下为 O(nm)O(n \cdot m)
  2. 其他变量

    • 程序中使用的其他变量(如计数器 count 和循环变量)占用常数空间。

综合考虑,程序的总时间复杂度为 O(n^2⋅m^2⋅n⋅m)=O(n^3⋅m^3)O(n^2⋅m^2⋅n⋅m)=O(n^3⋅m^3)。

总结

该程序通过四重循环枚举所有可能的子矩阵,并利用哈希集合来检查每个子矩阵中的字符唯一性。算法的时间复杂度为 O(n^3⋅m^3),空间复杂度为 O(n⋅m)。

优化建议

  1. 减少重复计算

    • 当前算法在检查每个子矩阵时都从头开始遍历,可以考虑使用动态规划或其他方法来减少重复计算。
  2. 优化哈希集合的使用

    • 当前算法在每次调用 isValidSubmatrix 时都重新初始化哈希集合,可以考虑在主循环中维护一个全局的哈希集合,以减少重复初始化的开销。
  3. 并行化处理

    • 对于大规模矩阵,可以考虑将子矩阵的检查任务分配到多个线程或处理器上,以提高计算效率。

由于该算法直接枚举所有可能的子矩阵并检查它们,因此它不是针对大规模数据的最优解。但是,对于小到中等规模的矩阵,它是可行的。对于大规模矩阵,可能需要更高效的算法,例如使用动态规划或滑动窗口等优化技术。