共同高频字符统计
问题背景
我们需要在一组给定的字符串中,找出那些在每一个字符串里都满足特定出现次数要求的“共同高频字符”。
筛选规则
一个字符若要被视为“共同高频字符”,它必须满足以下条件:
对于给定的所有字符串,该字符在每一个字符串中的出现次数,都大于或等于一个指定的阈值 count。
换言之,这个条件必须在所有字符串上同时成立,任何一个字符串不满足,该字符就会被淘汰。
任务要求
给定一个出现次数阈值 count 和一个字符串列表 strings,请找出所有符合上述规则的字符,并按其 ASCII 码升序排列后输出。
输入格式
-
count: 第一个参数,一个整数,表示要求的最小出现次数。1 <= count <= 100
-
strings: 第二个参数,一个字符串列表。1 <= strings.length <= 1001 <= strings[i].length < 1000- 字符串
strings[i]仅由英文字母和数字组成。
输出格式
- 一个字符列表(或字符串列表),其中包含所有符合要求的字符,并按 ASCII 码升序排列。
- 如果没有符合要求的字符,则输出一个空列表
[]。
样例说明
样例 1
-
输入:
count = 2strings = ["aabbccFFFFx2x2", "aaccddFFFFx2x2", "aabcdFFFFx2x2"]
-
输出:
["2", "F", "a", "x"] -
解释:
我们需要检查哪些字符在所有三个字符串中的出现次数都大于等于 2。
-
字符
a:- 在第1个字符串中出现 2 次 (>=2)
- 在第2个字符串中出现 2 次 (>=2)
- 在第3个字符串中出现 2 次 (>=2)
- -> 符合要求
-
字符
b:- 在第2个字符串中出现 0 次 (<2)
- -> 不符合要求 (无需再检查其他字符串)
-
字符
c:- 在第3个字符串中出现 1 次 (<2)
- -> 不符合要求
-
字符
d:- 在第1个字符串中出现 0 次 (<2)
- -> 不符合要求
-
字符
F:- 在第1个字符串中出现 4 次 (>=2)
- 在第2个字符串中出现 4 次 (>=2)
- 在第3个字符串中出现 4 次 (>=2)
- -> 符合要求
-
字符
x:- 在第1个字符串中出现 2 次 (>=2)
- 在第2个字符串中出现 2 次 (>=2)
- 在第3个字符串中出现 2 次 (>=2)
- -> 符合要求
-
字符
2:- 在第1个字符串中出现 2 次 (>=2)
- 在第2个字符串中出现 2 次 (>=2)
- 在第3个字符串中出现 2 次 (>=2)
- -> 符合要求
最终,符合要求的字符集合是
{a, F, x, 2}。按 ASCII 码升序排列后(2<F<a<x),输出为["2", "F", "a", "x"]。 -
样例 2
-
输入:
count = 2strings = ["aa", "bb", "cc"]
-
输出:
[] -
解释:
- 字符
a在字符串"bb"和"cc"中出现的次数为 0,不满足>= 2的要求。 - 字符
b在字符串"aa"和"cc"中出现的次数为 0,不满足要求。 - 字符
c在字符串"aa"和"bb"中出现的次数为 0,不满足要求。 - 没有任何字符在所有字符串中都满足出现至少 2 次的条件,因此输出空列表。
- 字符
import java.util.*;
/**
* 解决“公共字符”问题的方案类。
*/
public class Solution {
/**
* 查找在一组字符串中,所有字符串都至少出现了 count 次的公共字符。
*
* 算法思想:频率交集法
* 1. **基准频率统计**: 首先,统计第一个字符串 (`strings[0]`) 中每个字符的出现频率。
* 这个频率是我们最初的“候选标准”。一个字符如果连在第一个字符串中出现的次数都
* 不满足 `count`,那它肯定不是最终答案。我们用一个数组 `minFrequencies` 来存储
* 到目前为止,每个字符在所有已处理字符串中出现的**最小频率**。
*
* 2. **频率求交集**: 遍历剩下的每一个字符串 (`strings[1]`, `strings[2]`, ...)。
* a. 对当前字符串,也计算一个临时的字符频率表 `currentFrequencies`。
* b. 然后,更新 `minFrequencies` 数组。对于每个字符,它新的最小频率是
* `Math.min(旧的最小频率, 当前字符串中的频率)`。
* c. 这样,每处理一个字符串,`minFrequencies` 中存储的就是该字符在
* **所有已处理过的字符串中**的最小出现次数。
*
* 3. **筛选结果**: 遍历完所有字符串后,`minFrequencies` 数组就包含了每个字符在
* **所有**字符串中的最小出现次数。我们再次遍历这个数组,找出所有频率
* `>= count` 的字符。
*
* 4. **排序和输出**: 由于我们是按字符的 ASCII 码顺序(即数组索引 `0` 到 `127`)来检查
* `minFrequencies` 的,所以找到的符合条件的字符自然就是按 ASCII 码升序的。
* 将这些字符转换为字符串,添加到结果列表中即可。
*
* @param count 要求的最小出现次数。
* @param strings 待检查的字符串数组。
* @return 按ASCII码升序排列的、所有符合要求的字符的列表。
*/
public List<String> findCommonChars(int count, String[] strings) {
// --- 1. 处理边界情况 ---
if (strings == null || strings.length == 0) {
return new ArrayList<>(); // 如果没有字符串,则没有公共字符
}
if (count < 0) {
count = 0; // 保证 count 非负
}
// --- 2. 初始化最小频率数组 ---
// 使用一个大小为 128 的数组来覆盖所有基本的 ASCII 字符 (包括数字和大小写字母)
// minFrequencies[i] 表示字符 (char)i 在所有已遍历字符串中的最小出现次数
int[] minFrequencies = new int[128];
// a. 计算第一个字符串的字符频率作为基准
String firstString = strings[0];
for (char c : firstString.toCharArray()) {
// 确保字符在数组范围内,虽然题目保证了字母和数字
if (c < 128) {
minFrequencies[c]++;
}
}
// --- 3. 遍历剩余字符串,更新最小频率 (取交集) ---
for (int i = 1; i < strings.length; i++) {
// 为当前字符串创建一个临时的频率统计数组
int[] currentFrequencies = new int[128];
for (char c : strings[i].toCharArray()) {
if (c < 128) {
currentFrequencies[c]++;
}
}
// 更新 minFrequencies 数组
// 对于每个可能的字符,其新的最小频率是旧的最小频率和当前频率中的较小者
for (int j = 0; j < 128; j++) {
minFrequencies[j] = Math.min(minFrequencies[j], currentFrequencies[j]);
}
}
// --- 4. 筛选并收集结果 ---
List<String> result = new ArrayList<>();
// 遍历所有可能的 ASCII 字符 (0-127)
for (int i = 0; i < 128; i++) {
// 如果某个字符在所有字符串中的最小出现次数都大于等于 count
if (minFrequencies[i] >= count) {
// 将该字符转换为字符串并添加到结果列表中
result.add(String.valueOf((char) i));
}
}
// 由于我们是按 ASCII 码 (数组索引 i) 的升序遍历的,
// 所以 result 列表中的字符自然就是按 ASCII 码升序排列的,无需额外排序。
return result;
}
}
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 读取第一行:count
int count = Integer.parseInt(scanner.nextLine().trim());
// 读取第二行:字符串数组
String line = scanner.nextLine();
// 调用辅助函数解析形如 ["a", "b"] 的字符串
String[] strings = parseStringArray(line);
scanner.close();
// 创建 Solution 类的实例并调用核心方法
Solution solution = new Solution();
List<String> resultList = solution.findCommonChars(count, strings);
// 按题目要求的格式打印输出
System.out.println(formatListToString(resultList));
}
/**
* 辅助方法:解析形如 ["str1", "str2", ...] 的输入字符串。
* @param line 输入行
* @return 字符串数组
*/
private static String[] parseStringArray(String line) {
if (line == null || line.length() <= 2) { // 至少应包含 "[]"
return new String[0];
}
// 移除首尾的 '[' 和 ']'
line = line.substring(1, line.length() - 1).trim();
if (line.isEmpty()) {
return new String[0];
}
// 按 ", " 或 "," 分割,并去除每个元素首尾的引号 "
return Arrays.stream(line.split(","))
.map(s -> s.trim().replaceAll(""", ""))
.toArray(String[]::new);
}
/**
* 辅助方法:将字符串列表格式化为 ["a", "b", ...] 的形式。
* @param list 待格式化的列表
* @return 格式化后的字符串
*/
private static String formatListToString(List<String> list) {
if (list == null || list.isEmpty()) {
return "[]";
}
// 使用 String.join 和 stream API 简洁地构建输出字符串
return "[" + String.join(", ", list.stream()
.map(s -> """ + s + """)
.collect(Collectors.toList())) + "]";
}
}