普通网友 2025-12-16 14:10 采纳率: 98.6%
浏览 0
已采纳

洛谷 B3745 中如何高效统计手牌数量?

在洛谷 B3745 “统计手牌”问题中,如何高效统计每种花色和点数的出现次数?常见问题是:输入为若干张扑克牌(如H10、S2),需统计各点数(A~K)和花色(H、S、D、C)的数量。初学者常使用字符串分割与多重条件判断,导致代码冗长且效率低下。如何利用哈希表或数组映射实现快速计数?特别是点数“A”到“K”的转换与边界处理,易引发索引错误或漏统计。如何设计数据结构,在 O(n) 时间内完成统计并正确输出结果?这是本题的关键技术难点。
  • 写回答

1条回答 默认 最新

  • Qianwei Cheng 2025-12-16 14:10
    关注

    洛谷 B3745 “统计手牌”问题的高效解决方案

    1. 问题背景与核心挑战

    在洛谷题号 B3745 的“统计手牌”问题中,输入是一组表示扑克牌的字符串(如 H10、S2),每张牌由花色(H、S、D、C)和点数(A, 2~10, J, Q, K)组成。目标是分别统计每种花色和每种点数的出现次数。

    初学者常采用 split() 拆分字符串,并通过多个 if-elseswitch-case 判断处理点数映射,这种方式不仅代码冗长,且容易因边界处理不当导致索引越界或漏统计。

    真正的技术难点在于:如何设计一个时间复杂度为 O(n) 的算法,使用合适的数据结构避免重复判断,同时正确处理“A”、“J”、“Q”、“K”等非数字字符的映射问题。

    2. 常见实现方式及其缺陷分析

    • 字符串分割 + 条件判断:对每个输入串进行切片,分离花色和点数部分,再逐个判断点数值。例如用 if 判断是否为 "A",再转为 1,效率低且可读性差。
    • 正则表达式提取:虽能准确匹配,但在简单场景下引入额外开销,且不利于性能优化。
    • 未统一数据类型:将点数直接当作整数处理时忽略字母牌,导致运行时错误或遗漏统计。

    这些方法共同的问题是缺乏结构化映射机制,无法在 O(1) 时间内完成字符到索引的转换。

    3. 高效解决方案的设计思路

    为了实现 O(n) 时间复杂度的统计,应采用哈希表数组映射来预定义点数与索引之间的关系。

    关键步骤如下:

    1. 建立花色计数数组,大小为 4(对应 H、S、D、C);
    2. 建立点数计数数组,大小为 13(对应 A, 2~10, J, Q, K);
    3. 定义映射表(map 或 dict),将点数字符串映射到 0~12 的整数索引;
    4. 遍历每张牌,解析花色和点数,查表后更新对应计数器。

    4. 数据结构设计与映射策略

    点数符号映射索引
    A0
    21
    32
    43
    54
    65
    76
    87
    98
    109
    J10
    Q11
    K12

    花色可通过字符直接索引:

    char suit = card[0]; // H, S, D, C
    int suitIndex = "HSDDC".find(suit); // 实际可用 map 映射
    

    5. 代码实现示例(C++)

    #include <iostream>
    #include <vector>
    #include <map>
    #include <string>
    
    using namespace std;
    
    int main() {
        map<char, int> suitMap = {{'H', 0}, {'S', 1}, {'D', 2}, {'C', 3}};
        map<string, int> rankMap = {{"A",0},{"2",1},{"3",2},{"4",3},{"5",4},
                                     {"6",5},{"7",6},{"8",7},{"9",8},{"10",9},
                                     {"J",10},{"Q",11},{"K",12}};
    
        vector<int> suitCount(4, 0);
        vector<int> rankCount(13, 0);
    
        int n;
        cin >> n;
    
        for (int i = 0; i < n; ++i) {
            string card;
            cin >> card;
    
            char suit = card[0];
            string rank = card.substr(1); // 处理两位数的10
    
            if (suitMap.find(suit) != suitMap.end()) {
                suitCount[suitMap[suit]]++;
            }
    
            if (rankMap.find(rank) != rankMap.end()) {
                rankCount[rankMap[rank]]++;
            }
        }
    
        // 输出花色统计
        for (int i = 0; i < 4; ++i) {
            cout << suitCount[i] << " ";
        }
        cout << endl;
    
        // 输出点数统计
        for (int i = 0; i < 13; ++i) {
            cout << rankCount[i] << " ";
        }
        cout << endl;
    
        return 0;
    }
    

    6. 算法流程图(Mermaid)

    graph TD
        A[开始] --> B[读取牌数量n]
        B --> C{i < n?}
        C -- 是 --> D[读取一张牌card]
        D --> E[提取花色suit = card[0]]
        D --> F[提取点数rank = card.substr(1)]
        E --> G[查suitMap得索引]
        G --> H[花色计数+1]
        F --> I[查rankMap得索引]
        I --> J[点数计数+1]
        H --> K[i++]
        J --> K
        K --> C
        C -- 否 --> L[输出花色统计]
        L --> M[输出点数统计]
        M --> N[结束]
    

    7. 边界处理与健壮性增强

    实际应用中需考虑以下异常情况:

    • 输入字符串长度为0或格式错误(如空串、单字符);
    • 点数字段为“1”而无后续数字(误判为10的一部分);
    • 大小写混合(如 h10)——建议统一转大写;
    • 非法花色或点数(如 X9)——应跳过或报错。

    可通过增加输入校验层提升程序鲁棒性。

    8. 性能对比与扩展思考

    相比传统多重判断,使用映射表的优势包括:

    • 时间复杂度稳定为 O(n),每次查找为 O(1) 平均情况;
    • 维护成本低,新增点数或花色只需修改映射表;
    • 可复用性强,该模式适用于所有符号到索引的映射场景(如棋类、状态机);
    • 便于调试,计数数组可直接打印观察分布。

    进一步可扩展为支持多副牌、通配符(如 Joker)、甚至概率分析模块。

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

问题事件

  • 已采纳回答 12月17日
  • 创建了问题 12月16日