【华为OD机试真题】斗地主跑得快 · 最长顺子判定(Python)
·
一、题目
1. 题目描述
斗地主起源于湖北十堰房县,据说是一位叫吴修全的年轻人根据当地流行的扑克玩法“跑得快”改编的,如今已风靡整个中国,并流行于互联网上。
牌型定义(顺子):
- 又称顺子,最少 5 张牌,最多 12 张牌。
- 牌面范围:3...A。
- 限制条件:不能包含 2,也不能包含大小王。
- 花色规则:不计花色(即只看牌面值)。
示例顺子:
3-4-5-6-7-87-8-9-10-J-Q3-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(NlogN)
- 主要耗时在
sorted()排序上。由于扑克牌数量 N 极小(最多 54),实际运行速度极快,接近 O(1)。
- 主要耗时在
- 空间复杂度: O(N)
- 用于存储去重后的列表和映射字典。
六、总结🚀
这道题是考察基础数据处理能力的经典题目。Python 凭借其简洁的语法和强大的内置数据结构(Set, Dict),能让解题代码变得非常短小精悍且易读。
更多推荐


所有评论(0)