共同高频字符统计

130 阅读6分钟

共同高频字符统计

问题背景

我们需要在一组给定的字符串中,找出那些在每一个字符串里都满足特定出现次数要求的“共同高频字符”。

筛选规则

一个字符若要被视为“共同高频字符”,它必须满足以下条件:

对于给定的所有字符串,该字符在每一个字符串中的出现次数,都大于或等于一个指定的阈值 count

换言之,这个条件必须在所有字符串上同时成立,任何一个字符串不满足,该字符就会被淘汰。

任务要求

给定一个出现次数阈值 count 和一个字符串列表 strings,请找出所有符合上述规则的字符,并按其 ASCII 码升序排列后输出。


输入格式

  • count: 第一个参数,一个整数,表示要求的最小出现次数。

    • 1 <= count <= 100
  • strings: 第二个参数,一个字符串列表。

    • 1 <= strings.length <= 100
    • 1 <= strings[i].length < 1000
    • 字符串 strings[i] 仅由英文字母和数字组成。

输出格式

  • 一个字符列表(或字符串列表),其中包含所有符合要求的字符,并按 ASCII 码升序排列。
  • 如果没有符合要求的字符,则输出一个空列表 []

样例说明

样例 1

  • 输入:

    • count = 2
    • strings = ["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 = 2
    • strings = ["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())) + "]";
    }
}