一、题目

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 张连续的牌,故无法构成顺子。

二、解题思路

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 = 0best_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),能让解题代码变得非常短小精悍且易读。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐