当前位置: 首页 > news >正文

【华为OD机试真题】斗地主跑得快 · 最长顺子判定(C语言)

一、题目

1. 题目描述

斗地主起源于湖北十堰房县,据说是一位叫吴修全的年轻人根据当地流行的扑克玩法“跑得快”改编的,如今已风靡整个中国,并流行于互联网上。

牌型定义(顺子):

  • 又称顺子,最少 5 张牌,最多 12 张牌。
  • 牌面范围:3...A。
  • 限制条件:不能包含 2,也不能包含大小王。
  • 花色规则:不计花色(即只看牌面值)。

示例顺子:

  • 3-4-5-6-7-8
  • 7-8-9-10-J-Q
  • 3-4-5-6-7-8-9-10-J-Q-K-A

可用牌的大小顺序:
3 < 4 < 5 < 6 < 7 < 8 < 9 < 10 < J < Q < K < A < 2 < B(小王) < C(大王)
每种牌除大小王外有四种花色(共有 13×4+213×4+2 张牌)。

2. 输入描述

  • 第一行:当前手中的牌(字符串形式,用-分隔,如3-3-4-5...)。
  • 第二行:已经出过的牌(包括对手出的和自己出的牌,格式同上)。

3. 输出描述

  • 输出最长的顺子
  • 判定规则
    1. 优先选择长度最长的顺子。
    2. 如果有多个相同长度的顺子,输出牌面最大的那一个(即起始牌最大的那个)。
    3. 如果无法构成顺子(长度不足 5 或无连续牌),则输出NO-CHAIN

4. 示例数据

示例 1

输入:

3-3-3-4-4-5-5-6-7-8-9-10-J-Q-K-A-A-A-A 4-5-6-7-8-8-8

输出:

9-10-J-Q-K

解析:
手牌减去已出牌后,剩余牌中包含9, 10, J, Q, K,构成长度为 5 的顺子。虽然也有3-4-5-6-7等,但9开头的顺子牌面更大。

示例 2

输入:

3-3-3-3-8-8-8-8 K-K-K-K

输出

NO-CHAIN

解析:
剩余牌为3,3,3,3,8,8,8,8,无法凑齐 5 张连续的牌,故无法构成顺子。

二、核心解题思路 (C 语言特化)💡

1. 牌面数字化 (Mapping)

C 语言无法直接比较'J''10'的大小。我们需要建立一个映射表,将字符串转换为0~11的整数索引:

  • 3→0,4→1, ...,9→6,10→7,J→8,Q→9,K→10,A→11。
  • 技巧:使用两个平行数组CARD_STR[]和查找函数,或者简单的if-else/switch进行转换。

2. 布尔标记数组 (Boolean Flag Array)

由于牌面种类固定且很少(仅12种),我们不需要动态链表或哈希表。

  • 定义一个长度为 12 的整型数组int has_card[12]
  • 去重逻辑
    1. 解析手牌,将对应的索引位置标记为1(has_card[idx] = 1)。重复标记无所谓,天然去重。
    2. 解析已出牌,将对应的索引位置标记为0(has_card[idx] = 0)。
  • 结果has_card数组变成了一个连续性位图1代表该点数的牌可用,0代表不可用。

3. 线性扫描找最长连续段

遍历has_card[0...11]

  • 维护current_len(当前连续长度) 和current_start(当前起点)。
  • 遇到1:长度 +1。
  • 遇到0或结束:结算上一段。
    • current_len >= 5,则对比最优解。
    • 优先级策略
      • current_len > best_len:更新最优解。
      • current_len == best_len:由于我们是从左向右(从小到大)遍历,后遇到的连续段起点一定更大。因此,只要长度相等,直接覆盖即可满足“起始牌最大”的要求。
      • 简化逻辑if (current_len >= best_len)即可更新。

三、C 语言完整代码实现

#include <stdio.h> #include <string.h> #include <stdlib.h> // 1. 定义牌面映射表 const char *CARD_STR[] = {"3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"}; #define CARD_COUNT 12 // 辅助函数:将字符串牌面转为索引 (0-11),失败返回 -1 int get_card_index(const char *card) { for (int i = 0; i < CARD_COUNT; i++) { if (strcmp(card, CARD_STR[i]) == 0) { return i; } } return -1; } // 辅助函数:解析字符串并更新标记数组 // flag: 1 表示添加手牌,0 表示移除已出牌 void parse_and_update(const char *input_str, int *available, int flag) { if (input_str == NULL || strlen(input_str) == 0) return; // 复制字符串以便 strtok 修改 (strtok 会破坏原字符串) char *buffer = strdup(input_str); if (!buffer) return; char *token = strtok(buffer, "-"); while (token != NULL) { int idx = get_card_index(token); if (idx != -1) { available[idx] = flag; // 注意:如果是添加手牌,多次出现设为1即可(去重) // 如果是移除,设为0即可(即使原本没有也不影响) } token = strtok(NULL, "-"); } free(buffer); } void solve() { char hand_line[1024]; char played_line[1024]; // 读取两行输入 if (!fgets(hand_line, sizeof(hand_line), stdin)) return; if (!fgets(played_line, sizeof(played_line), stdin)) return; // 去除换行符 hand_line[strcspn(hand_line, "\n")] = 0; played_line[strcspn(played_line, "\n")] = 0; // 2. 初始化可用牌标记数组 (0:不可用, 1:可用) int available[CARD_COUNT] = {0}; // 3. 处理手牌 (标记为 1) parse_and_update(hand_line, available, 1); // 4. 处理已出牌 (标记为 0) parse_and_update(played_line, available, 0); // 5. 线性扫描寻找最长顺子 int best_len = 0; int best_start = -1; int current_len = 0; int current_start = -1; for (int i = 0; i < CARD_COUNT; i++) { if (available[i] == 1) { if (current_len == 0) { current_start = i; } current_len++; } else { // 连续性中断,结算 if (current_len >= 5) { // 核心逻辑:长度更长,或者长度相等(此时当前起点一定更大) if (current_len >= best_len) { best_len = current_len; best_start = current_start; } } current_len = 0; current_start = -1; } } // 处理末尾连续的片段 if (current_len >= 5) { if (current_len >= best_len) { best_len = current_len; best_start = current_start; } } // 6. 输出结果 if (best_len == 0) { printf("NO-CHAIN\n"); } else { // 构造输出字符串 // 题目限制最多12张,这里直接输出找到的连续段即可 for (int i = 0; i < best_len; i++) { printf("%s", CARD_STR[best_start + i]); if (i < best_len - 1) { printf("-"); } } printf("\n"); } } int main() { solve(); return 0; }

四、代码深度解析🔍

1. 字符串解析 (strtok)

C 语言处理-分隔的字符串,标准做法是使用<string.h>中的strtok函数。

  • 注意strtok会修改原字符串,所以先用strdup复制一份输入字符串到堆内存,处理完后记得free,防止内存泄漏。
  • 兼容性strdup是 POSIX 标准函数,大多数 OJ 环境(包括华为 OD)都支持。如果环境极其严格不支持strdup,可以手动malloc+strcpy

2. 标记数组的去重魔法

int available[CARD_COUNT] = {0}; // 手牌来了:available[idx] = 1; // 又一张同样的牌:available[idx] = 1; (值不变,天然去重) // 已出牌来了:available[idx] = 0; (无论之前是几,现在都没了)

这种状态标记法避免了复杂的计数逻辑,完美契合“顺子只看有无,不看数量”的规则。

3. 优先级判定的巧妙简化

题目要求:长度相同,起始牌越大越好

  • 我们的循环是for (int i = 0; i < 12; i++),即从3扫到A
  • 假设先发现3-4-5-6-7(len=5, start=0)。
  • 后发现9-10-J-Q-K(len=5, start=5)。
  • 当扫到第二段时,current_len (5) >= best_len (5)成立。
  • 执行更新:best_start变为 5。
  • 结论:因为遍历方向是从小到大,后出现的同长度顺子,其起点必然更大。所以直接用>=覆盖,即可同时满足“最长”和“同长取大”两个条件。

4. 内存安全

  • 使用fgets替代scanf读取整行,避免缓冲区溢出和处理空格分隔的麻烦。
  • strcspn用于快速去除末尾的换行符\n

五、避坑指南⚠️

  1. "10" 的特殊性
    • 10是两个字符,不能用char存储,必须用char*string比较。代码中使用strcmp完美解决。
  2. 输入为空或格式错误
    • 代码中增加了strlen检查和fgets返回值检查,增强鲁棒性。
  3. 边界情况
    • 如果最长顺子一直延续到数组最后一个元素 (A, index 11),循环内的else分支不会触发。必须在循环结束后单独判断一次current_len,否则会漏掉以A结尾的顺子。
  4. 无解输出
    • 务必检查best_len是否仍为 0,若是则输出NO-CHAIN

六、复杂度分析📊

  • 时间复杂度
    • 字符串解析: O(N+M) ,其中 N,M 分别为手牌和已出牌的字符串长度。
    • 查找顺子:固定循环 12 次, O(1) 。
    • 总计: O(N+M) ,线性效率。
  • 空间复杂度
    • 标记数组固定 12 个整数, O(1) 。
    • 字符串缓冲区取决于输入长度,但在处理过程中是线性的。

七、总结

这道题在 C 语言中考察了三个关键点:

  1. 数据映射:如何将非数字的枚举类型(扑克牌)转化为可计算的整数索引。
  2. 状态压缩:利用小规模固定数组代替复杂的集合数据结构。
  3. 逻辑简化:利用遍历顺序的特性,将复杂的多条件排序简化为单次扫描覆盖。

掌握这种“映射 + 标记数组 + 一次扫描”的模式,不仅能解决扑克牌问题,还能应对各类“最长连续子序列”、“区间合并”等算法题。祝各位 C 语言选手机考顺利,代码一次 AC!

http://www.jsqmd.com/news/525360/

相关文章:

  • AI原生应用情境感知的未来展望
  • 悠哉字体:一款让中文排版更“悠然自得“的开源手写字体
  • 内容发表前必须改写吗?3年实测告诉你:AI率超标,再优质的内容也白搭
  • 通义千问3-4B-Instruct-2507长文本处理:实测80万汉字文档,提取核心信息So Easy
  • Soybean Admin永久关闭git校验的3步操作(附pnpm命令详解)
  • 实战对比:pcolormesh vs imshow - 数据可视化如何选对工具?
  • 基于混合A*算法的泊车路径规划探索
  • Llama-3.2V-11B-cot 作品集:从设计草图到产品说明书的自动生成
  • GMS认证测试全攻略:CTS/VTS/STS/GSI命令详解与SMR白名单申请实战
  • 三相逆变器PR控制实战:从Simulink仿真到离网应用避坑指南
  • Qwen2.5-VL视觉定位作品集:从日常物品到复杂场景的精确定位
  • SolidWorks 异形孔向导命令 - 柱形沉头孔
  • 三步构建专业级AI投资决策系统:TradingAgents-CN多智能体金融分析框架深度解析
  • OpenClaw技能扩展:基于GLM-4.7-Flash实现Markdown文档自动整理
  • StructBERT中文相似度模型基础教程:中文分词器适配与tokenization优化
  • OpCore Simplify:突破性重构开源系统定制的跨平台兼容性解决方案
  • ShareX截图工具报错:ffmpeg.exe缺失的快速修复指南2023
  • BIOS高级设置技术突破:硬件爱好者的性能释放实战指南
  • 【一篇即毕业系列】RAII管理从基础到通天!!看这一篇就够了!!
  • 1258:【例9.2】数字金字塔 回溯搜索(超时)解法示例
  • Comsol 中的随机激光:奇妙的微观能量之旅
  • 2026高阻燃热缩管优质供应商推荐指南:PVDF热缩套管/PVDF热缩管/密封防水热缩套管/密封防水热缩管/异形热缩套管/选择指南 - 优质品牌商家
  • Cursor配置GitHub MCP Server避坑指南:个人访问令牌(PAT)的正确生成与安全使用
  • HY-Motion 1.0实战:用一句话生成虚拟偶像跳舞动作
  • 风光储三相PQ并网系统实战手记
  • SAP 批量处理分包事后调整:BAPI_GOODSMVT_CREATE 关键参数与避坑指南
  • translategemma-4b-it效果实测:Ollama环境下对模糊/低清/倾斜图片的鲁棒性翻译表现
  • 如何快速构建黑苹果EFI:OpCore Simplify自动化配置指南
  • Claude Code配置和使用 - fx
  • Rust的匹配中的通配符模式与变量绑定在模式忽略中的语义区别