一、题目
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(NlogN)O(NlogN)
- 主要耗时在 sorted() 排序上。由于扑克牌数量 NN 极小(最多 54),实际运行速度极快,接近 O(1)O(1) 。
- 空间复杂度: O(N)O(N)
- 用于存储去重后的列表和映射字典。
六、总结🚀
这道题是考察基础数据处理能力的经典题目。Python 凭借其简洁的语法和强大的内置数据结构(Set, Dict),能让解题代码变得非常短小精悍且易读。