在洛谷 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-else或switch-case判断处理点数映射,这种方式不仅代码冗长,且容易因边界处理不当导致索引越界或漏统计。真正的技术难点在于:如何设计一个时间复杂度为 O(n) 的算法,使用合适的数据结构避免重复判断,同时正确处理“A”、“J”、“Q”、“K”等非数字字符的映射问题。
2. 常见实现方式及其缺陷分析
- 字符串分割 + 条件判断:对每个输入串进行切片,分离花色和点数部分,再逐个判断点数值。例如用 if 判断是否为 "A",再转为 1,效率低且可读性差。
- 正则表达式提取:虽能准确匹配,但在简单场景下引入额外开销,且不利于性能优化。
- 未统一数据类型:将点数直接当作整数处理时忽略字母牌,导致运行时错误或遗漏统计。
这些方法共同的问题是缺乏结构化映射机制,无法在 O(1) 时间内完成字符到索引的转换。
3. 高效解决方案的设计思路
为了实现 O(n) 时间复杂度的统计,应采用哈希表或数组映射来预定义点数与索引之间的关系。
关键步骤如下:
- 建立花色计数数组,大小为 4(对应 H、S、D、C);
- 建立点数计数数组,大小为 13(对应 A, 2~10, J, Q, K);
- 定义映射表(map 或 dict),将点数字符串映射到 0~12 的整数索引;
- 遍历每张牌,解析花色和点数,查表后更新对应计数器。
4. 数据结构设计与映射策略
点数符号 映射索引 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 花色可通过字符直接索引:
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)、甚至概率分析模块。
本回答被题主选为最佳回答 , 对您是否有帮助呢?解决 无用评论 打赏 举报