斗地主之顺子

一、题目描述

在斗地主扑克牌游戏中, 扑克牌由小到大的顺序为:3,4,5,6,7,8,9,10,J,Q,K,A,2,玩家可以出的扑克牌阵型有:单张、对子、顺子、飞机、炸弹等。

其中顺子的出牌规则为:由至少5张由小到大连续递增的扑克牌组成,且不能包含2。

例如:{3,4,5,6,7}、{3,4,5,6,7,8,9,10,J,Q,K,A}都是有效的顺子;而{J,Q,K,A,2}、 {2,3,4,5,6}、{3,4,5,6}、{3,4,5,6,8}等都不是顺子。

给定一个包含13张牌的数组,如果有满足出牌规则的顺子,请输出顺子。

如果存在多个顺子,请每行输出一个顺子,且需要按顺子的第一张牌的大小(必须从小到大)依次输出。

如果没有满足出牌规则的顺子,请输出No。

二、输入输出描述

输入描述
  • 13张任意顺序的扑克牌,每张扑克牌数字用空格隔开,每张扑克牌的数字都是合法的,并且不包括大小王。
  • 不需要考虑输入为异常字符的情况
输出描述
  • 每行输出一个合法顺子(牌数字空格分隔),无则输出 No。

三、示例

|----|------------------------------|
| 输入 | 2 9 J 2 3 4 K A 7 9 A 5 6 |
| 输出 | 3 4 5 6 7 |
| 说明 | 13张牌中,可以组成的顺子只有1组:3 4 5 6 7。 |

|----|-------------------------------------------------|
| 输入 | 2 9 J 10 3 4 K A 7 Q A 5 6 |
| 输出 | 3 4 5 6 7 9 10 J Q K A |
| 说明 | 13张牌中,可以组成2组顺子,从小到大分别为:3 4 5 6 7 和 9 10 J Q K A |

四、解题思路

1. 核心思想

将扑克牌面数字化后排序,通过 "贪心匹配连续数值" 构建顺子序列,筛选出长度≥5 的有效顺子,最终按起始牌升序输出 ------ 核心是 "数值化简化比较 + 排序保证顺序 + 贪心构建连续序列"。

2. 问题本质分析
  • 表层问题:从无序扑克牌中识别并输出所有有效顺子(长度≥5、牌面连续);
  • 深层问题:
    1. 非数值型数据的比较:将字符牌面(如 J、Q)转化为数值,解决 "连续关系" 的判断问题;
    2. 连续序列的构建:在排序后的数组中,贪心匹配每个元素的连续后继,避免重复 / 遗漏顺子;
    3. 有效序列的筛选与排序:按长度和起始值筛选、排序,满足输出规则。
3. 核心逻辑
  • 数值化映射:将牌面转化为整数,把 "字符连续" 问题转化为 "数值差 = 1" 的数学判断,降低比较复杂度;
  • 排序预处理:对牌数组按数值升序排序,保证遍历顺序是 "从小到大",为贪心构建顺子提供基础;
  • 贪心构建顺子:遍历排序后的牌,优先将当前牌加入已有顺子的末尾(若连续),否则新建顺子,确保每个牌仅属于一个顺子;
  • 筛选与排序输出:保留长度≥5 的顺子,按起始牌数值升序输出。
4. 步骤拆解
  1. 牌面数值化映射

    • 定义静态 HashMap,将每张牌面映射为唯一整数(2 映射为 16,避免参与顺子)。
  2. 输入处理与排序

    • 读取输入的牌数组,按数值升序排序,保证遍历顺序为 "从小到大"。
  3. 贪心构建顺子序列

    • 初始化首个顺子(含第一张牌),存入顺子列表;
    • 遍历剩余牌,尝试将其加入已有顺子的末尾(需数值连续),无法加入则新建顺子。
  4. 筛选有效顺子

    • 过滤出长度≥5 的顺子,得到有效顺子列表。
  5. 结果输出

    • 无有效顺子则输出 "No";
    • 有则按起始牌数值升序,拼接并输出每个顺子。

五、代码实现

java 复制代码
import java.util.*;
import java.util.stream.Collectors;

public class Main {
    // 静态初始化一个用于映射扑克牌面到数字的HashMap,以方便后续比较大小
    private static final Map<String, Integer> CARD_TO_NUMBER;

    static {
        // 初始化HashMap
        CARD_TO_NUMBER = new HashMap<>();
        // 将每张扑克牌对应的面值映射到一个整数,其中2被认为是最大的牌
        CARD_TO_NUMBER.put("3", 3);
        CARD_TO_NUMBER.put("4", 4);
        CARD_TO_NUMBER.put("5", 5);
        CARD_TO_NUMBER.put("6", 6);
        CARD_TO_NUMBER.put("7", 7);
        CARD_TO_NUMBER.put("8", 8);
        CARD_TO_NUMBER.put("9", 9);
        CARD_TO_NUMBER.put("10", 10);
        CARD_TO_NUMBER.put("J", 11);
        CARD_TO_NUMBER.put("Q", 12);
        CARD_TO_NUMBER.put("K", 13);
        CARD_TO_NUMBER.put("A", 14);
        CARD_TO_NUMBER.put("2", 16);
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);  // 创建Scanner对象用于读取输入
        String[] cards = sc.nextLine().split(" ");  // 读取一行输入,并按空格分割成数组
        // 对输入的扑克牌按照定义的牌面大小进行排序
        Arrays.sort(cards, (a, b) -> CARD_TO_NUMBER.get(a) - CARD_TO_NUMBER.get(b));

        ArrayList<LinkedList<String>> straights = new ArrayList<>();  // 用于存储所有可能的顺子序列
        LinkedList<String> currentStraight = new LinkedList<>();  // 初始化当前正在检查的顺子序列
        currentStraight.add(cards[0]);  // 将排序后的第一张牌加入到当前顺子序列中
        straights.add(currentStraight);  // 将当前顺子序列加入到顺子列表中

        // 从第二张牌开始遍历所有牌
        for (int i = 1; i < cards.length; i++) {
            String card = cards[i];
            boolean added = false;  // 标记当前牌是否已被添加到某个顺子中

            // 遍历当前已存在的所有顺子序列,尝试将当前牌加入
            for (LinkedList<String> straight : straights) {
                // 判断当前牌是否可以追加到顺子的末尾
                if (CARD_TO_NUMBER.get(card) - CARD_TO_NUMBER.get(straight.getLast()) == 1) {
                    straight.add(card);
                    added = true;
                    break;
                }
            }

            // 如果当前牌没有加入到任何顺子中,创建一个新的顺子序列
            if (!added) {
                LinkedList<String> newStraight = new LinkedList<>();
                newStraight.add(card);
                straights.add(newStraight);
            }
        }

        // 筛选出长度至少为5的有效顺子序列
        List<LinkedList<String>> validStraights =
            straights.stream().filter(straight -> straight.size() >= 5).collect(Collectors.toList());

        // 如果没有找到有效的顺子序列,输出"No"
        if (validStraights.isEmpty()) {
            System.out.println("No");
        } else {
            // 将所有有效的顺子按照起始牌的大小进行排序并输出
            validStraights.stream()
                .sorted((a, b) -> CARD_TO_NUMBER.get(a.getFirst()) - CARD_TO_NUMBER.get(b.getFirst()))
                .forEach(straight -> System.out.println(String.join(" ", straight)));
        }
    }
}
相关推荐
Jerry3 小时前
LeetCode 28. 找出字符串中第一个匹配项的下标
算法
Jerry5 小时前
LeetCode 459. 重复的子字符串
算法
闲猫8 小时前
Spring AI 对接Deepseek ChatModel 聊天对话
java·前端·spring
海石8 小时前
1500分的题目,确实有实力,不过还是我略胜一筹
算法·leetcode
海石8 小时前
【记忆化搜索】条条大路通AC,走好适合你的那一条,走到后再考虑走得快
算法·leetcode
自信的未来10 小时前
JSON 工具|Web Worker 工程化打包 + 语法自动修复 + 多语言代码生成实战
java·前端·json
Brookty10 小时前
【JavaEE】线程安全(一).4:写块串行保安全、CAS
java·开发语言·java-ee·多线程·线程安全
我叫黑大帅10 小时前
MySQL 并发插入竞态问题:原子写入实践指南
后端·mysql·面试
Jerry10 小时前
LeetCode 151. 反转字符串中的单词
算法