欢迎光临
我们一直在努力

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

一、题目

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. 输出描述

  • 输出最长的顺子。
  • 判定规则:
  • 优先选择长度最长的顺子。
  • 如果有多个相同长度的顺子,输出牌面最大的那一个(即起始牌最大的那个)。
  • 如果无法构成顺子(长度不足 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 张连续的牌,故无法构成顺子。

二、解题思路

Python 在处理此类数据清洗和逻辑判断问题上具有天然优势,我们可以分三步走:

1. 数据清洗与映射 (Data Cleaning & Mapping)

输入是杂乱的字符串,我们需要将其转化为有序的数字序列。

  • 映射表:使用字典(dict)建立 {'3':3, …, 'J':11, …, 'A':14} 的映射。
  • 过滤与去重:遍历输入列表,跳过无效牌(2, B, C)。利用 Python 的 set 集合特性,自动完成去重,然后再转为列表并排序。

    Python 技巧:sorted(list(set(valid_nums))) 一行代码即可完成去重排序。

2. 线性扫描寻找最长连续子序列 (Linear Scan)

在有序且无重复的数字列表中,寻找最长的连续段。

  • 初始化 max_len = 0, best_start = -1。
  • 维护当前连续段的 current_len 和 current_start。
  • 遍历列表:
    • 若 nums[i] == nums[i-1] + 1:连续,current_len += 1。
    • 否则:断开。检查上一段是否满足 5 <= len <= 12,若满足且更长,则更新最大值。重置当前段计数。
  • 注意:循环结束后,务必再次检查最后一段(防止最长顺子在数组末尾被遗漏)。

3. 结果格式化 (Formatting)

  • 若 max_len < 5,返回 0。
  • 否则,利用逆映射字典将数字转回牌面字符串,使用 join 方法生成最终结果。

三、Code实现

import sys

def solve():
# 读取输入,兼容可能的空格
try:
line = sys.stdin.readline()
if not line:
return
cards_str = line.strip()
if not cards_str:
print(0)
return
card_list = [c.strip() for c in cards_str.split(',')]
except Exception:
print(0)
return

# 1. 建立映射关系
# 正向映射:牌面 -> 数字
card_to_num = {
'3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9, '10': 10,
'J': 11, 'Q': 12, 'K': 13, 'A': 14
}

# 逆向映射:数字 -> 牌面 (用于最后输出)
num_to_card = {v: k for k, v in card_to_num.items()}

# 2. 数据清洗:过滤无效牌,去重,排序
valid_nums = []
for c in card_list:
if c in card_to_num:
valid_nums.append(card_to_num[c])

# set 去重,sorted 排序
unique_sorted_nums = sorted(list(set(valid_nums)))

if not unique_sorted_nums:
print(0)
return

# 3. 线性扫描寻找最长顺子
max_len = 0
best_start = -1

current_len = 1
current_start = unique_sorted_nums[0]

for i in range(1, len(unique_sorted_nums)):
if unique_sorted_nums[i] == unique_sorted_nums[i-1] + 1:
# 连续
current_len += 1
else:
# 断开,检查上一段
if 5 <= current_len <= 12:
if current_len > max_len:
max_len = current_len
best_start = current_start
# 若长度相等,由于是从左向右遍历,保留较小的 start (无需操作)

# 重置
current_len = 1
current_start = unique_sorted_nums[i]

# 循环结束后,检查最后一段
if 5 <= current_len <= 12:
if current_len > max_len:
max_len = current_len
best_start = current_start

# 4. 输出结果
if max_len < 5:
print(0)
else:
# 构造顺子列表
result_cards = [num_to_card[best_start + i] for i in range(max_len)]
print("-".join(result_cards))

if __name__ == "__main__":
solve()

四、常见陷阱与测试用例⚠️

陷阱 1:大小王和 2 的处理

  • 错误做法:将 2 映射为 15,试图让 A, 2 连起来。
  • 正确做法:题目规定顺子不含 2 和大小王,必须在预处理阶段直接丢弃这些牌。

陷阱 2:重复牌的影响

  • 场景:输入 3,3,4,5,6,7。
  • 分析:如果不先去重,3,3 会被判定为不连续(3 != 3+1),导致顺子断裂。
  • 解决:必须先 set 去重,变为 3,4,5,6,7。

陷阱 3:最大长度限制

  • 虽然 3 到 A 只有 12 张牌,理论上不会超过 12,但在逻辑判断中显式写出 <= 12 是对题目规则的严格遵循,防止未来规则变更或特殊变体。

五、复杂度分析📊

  • 时间复杂度: O(Nlog⁡N)O(NlogN)
    • 主要耗时在 sorted() 排序上。由于扑克牌数量 NN 极小(最多 54),实际运行速度极快,接近 O(1)O(1) 。
  • 空间复杂度: O(N)O(N)
    • 用于存储去重后的列表和映射字典。

六、总结🚀

这道题是考察基础数据处理能力的经典题目。Python 凭借其简洁的语法和强大的内置数据结构(Set, Dict),能让解题代码变得非常短小精悍且易读。

赞(0)
未经允许不得转载:171主机测评 » 【华为OD机试真题】斗地主跑得快 · 最长顺子判定(Python)
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址