一、题目描述
在斗地主扑克牌游戏中, 扑克牌由小到大的顺序为: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、牌面连续);
- 深层问题:
- 非数值型数据的比较:将字符牌面(如 J、Q)转化为数值,解决 "连续关系" 的判断问题;
- 连续序列的构建:在排序后的数组中,贪心匹配每个元素的连续后继,避免重复 / 遗漏顺子;
- 有效序列的筛选与排序:按长度和起始值筛选、排序,满足输出规则。
3. 核心逻辑
- 数值化映射:将牌面转化为整数,把 "字符连续" 问题转化为 "数值差 = 1" 的数学判断,降低比较复杂度;
- 排序预处理:对牌数组按数值升序排序,保证遍历顺序是 "从小到大",为贪心构建顺子提供基础;
- 贪心构建顺子:遍历排序后的牌,优先将当前牌加入已有顺子的末尾(若连续),否则新建顺子,确保每个牌仅属于一个顺子;
- 筛选与排序输出:保留长度≥5 的顺子,按起始牌数值升序输出。
4. 步骤拆解
-
牌面数值化映射:
- 定义静态 HashMap,将每张牌面映射为唯一整数(2 映射为 16,避免参与顺子)。
-
输入处理与排序:
- 读取输入的牌数组,按数值升序排序,保证遍历顺序为 "从小到大"。
-
贪心构建顺子序列:
- 初始化首个顺子(含第一张牌),存入顺子列表;
- 遍历剩余牌,尝试将其加入已有顺子的末尾(需数值连续),无法加入则新建顺子。
-
筛选有效顺子:
- 过滤出长度≥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)));
}
}
}