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类图
数据结构
1. 数组 (Array)
String[] s:这是一个二维字符数组,表示输入的矩阵。每个元素是一个字符串,代表矩阵的一行。
2. 哈希集合 (HashSet)
Set<Character> letters:这是一个哈希集合,用于存储子矩阵中的字符。哈希集合的特点是不允许重复元素,这使得检查字符是否唯一变得非常高效。
详细说明
数组 (Array)
String[] s:这是一个一维数组,但每个元素是一个字符串,实际上形成了一个二维字符数组。例如:这个数组可以看作是一个 2x3 的矩阵: a a d a b cString[] s = {"aad", "abc"};
哈希集合 (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 的使用示例如下:
- 初始状态:
letters = {} - 处理子矩阵
s[0][0]到s[0][0](即字符 'a'):letters.add('a')->letters = {'a'}
- 处理子矩阵
s[0][0]到s[0][1](即字符 'a', 'a'):letters.add('a')->letters = {'a'}letters.contains('a')返回true,所以子矩阵不满足条件。
通过这种方式,哈希集合有效地帮助我们检查子矩阵中的字符是否唯一。
4.程序算法分析与总结
暴力枚举算法
算法分析
该程序的主要目的是计算给定矩阵中满足特定条件的子矩阵的数量。具体来说,这些子矩阵中的每个字符都必须是唯一的,即不能有重复的字符。程序使用了四重循环来遍历所有可能的子矩阵,并使用一个辅助函数来检查每个子矩阵是否满足条件。
时间复杂度分析
-
四重循环:
- 外层两重循环遍历所有可能的左上角坐标
(i, j)。 - 内层两重循环遍历所有可能的右下角坐标
(k, l)。 - 因此,四重循环的总时间复杂度为 ,其中 是矩阵的行数, 是矩阵的列数。
- 外层两重循环遍历所有可能的左上角坐标
-
辅助函数
isValidSubmatrix:- 该函数遍历子矩阵中的每个字符,并使用哈希集合来检查字符是否唯一。
- 子矩阵的大小范围是从 到 ,因此该函数在最坏情况下会被调用 次。
- 每次调用
isValidSubmatrix函数的时间复杂度为 ,即子矩阵的大小。
综合考虑,程序的总时间复杂度为 。在最坏情况下,子矩阵的大小可以达到 ,因此总时间复杂度可以简化为 。
空间复杂度分析
-
哈希集合
letters:- 在每次调用
isValidSubmatrix函数时,哈希集合最多存储子矩阵中的所有字符。 - 因此,空间复杂度在最坏情况下为 。
- 在每次调用
-
其他变量:
- 程序中使用的其他变量(如计数器
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)。
优化建议
-
减少重复计算:
- 当前算法在检查每个子矩阵时都从头开始遍历,可以考虑使用动态规划或其他方法来减少重复计算。
-
优化哈希集合的使用:
- 当前算法在每次调用
isValidSubmatrix时都重新初始化哈希集合,可以考虑在主循环中维护一个全局的哈希集合,以减少重复初始化的开销。
- 当前算法在每次调用
-
并行化处理:
- 对于大规模矩阵,可以考虑将子矩阵的检查任务分配到多个线程或处理器上,以提高计算效率。
由于该算法直接枚举所有可能的子矩阵并检查它们,因此它不是针对大规模数据的最优解。但是,对于小到中等规模的矩阵,它是可行的。对于大规模矩阵,可能需要更高效的算法,例如使用动态规划或滑动窗口等优化技术。