一、题目描述
在斗地主只扑克牌游戏中,扑克牌由小到大的顺序为: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}都是有效的顺子;
**而**
{Q,K,A,2}、(2,3,4,5,6}、(3,4,5,6}、(3,4,5,6,8)等都不是顺子。
给定一个包含13张牌的数组,如果有满足出牌规则的顺子,请输出顺子。
如果存在多个顺子,请每行输出一个顺子,且需要按顺子的第一张牌的大小(必须从小到大)依次输出。
如果没有满足出牌规则的顺子,请输出NO。
二、输入描述
13张任意顺序的扑克牌,每张扑克牌数字用空格隔开,每张扑克牌的数字都是合法的,并且不包括大小王:2 9 J 2 3 4 K A 7 9 A 5 6不需要考虑输入为异常字符的情况。
三、输出描述
组成的顺子,每张扑克牌数字用空格隔开:3 4 5 6 7
**示例1:**
输入:2 9 J 2 3 4 K A 7 9 A 5 6
输出:3 4 5 6 7
四、代码实现
public class PokerGame {
private static final Logger log = LoggerFactory.getLogger(PokerGame.class);
/**
* 查找并打印给定扑克牌序列中的所有有效顺子
* 顺子定义为连续的五张牌序列
*
* @param cards 以空格分隔的牌值字符串,每张牌用2位数字表示,如"03 04 05 06 07 08 09 10 11 12 01 02 01"
* 其中01代表A,11代表J,12代表Q,13代表K
*/
public static void findValidSequences(String cards) {
// 检查输入是否有效
if (cards == null || cards.isEmpty()) {
System.out.println("Invalid input.");
return;
}
log.info("Input: {}", cards);
// 将输入的字符串转换为整数数组并排序
int[] cardValues = new int[13];
String[] splitCards = cards.split(" ");
if (splitCards.length != 13) {
System.out.println("Invalid number of cards.");
return;
}
// 遍历splitCards数组,将每个元素转换为对应的牌值,并存储在cardValues数组中
for (int i = 0; i < splitCards.length; i++) {
cardValues[i] = parseCardValue(splitCards[i]);
}
//排序
Arrays.sort(cardValues);
// 查找并输出所有有效的顺子
List<List<Integer>> sequences = findSequences(cardValues);
if (sequences.isEmpty()) {
System.out.println("NO");
} else {
sequences.forEach(PokerGame::printSequence);
}
}
private static int parseCardValue(String cardStr) {
switch (cardStr) {
case "2": return 2;
case "3": return 3;
case "4": return 4;
case "5": return 5;
case "6": return 6;
case "7": return 7;
case "8": return 8;
case "9": return 9;
case "10": return 10;
case "J": return 11;
case "Q": return 12;
case "K": return 13;
case "A": return 14;
default: throw new IllegalArgumentException("Invalid card: " + cardStr);
}
}
private static List<List<Integer>> findSequences(int[] sortedCards) {
List<List<Integer>> sequences = new ArrayList<>();
// 从第0张牌开始,遍历所有可能的顺子
for (int i = 0; i <= sortedCards.length - 5; i++) {
// 检查当前位置是否为有效顺子
if (isSequence(sortedCards, i)) {
List<Integer> sequence = new ArrayList<>();
// 将当前顺子添加到结果列表中
for (int j = i; j < i + 5; j++) {
sequence.add(sortedCards[j]);
}
sequences.add(sequence);
// 跳过已使用的四张牌,只需移动到下一个待检查的起始位置
}
}
return sequences;
}
private static boolean isSequence(int[] cards, int start) {
int gap = 0; // 用于记录非顺序牌之间的差值
// 检查起始位置是否为2,如果是,则不是有效顺子
if (cards[start] == 2) {
return false;
}
// 遍历5张牌,检查是否为顺子
for (int i = start; i < start + 5; i++) {
// 检查是否出现重复牌
if (i > start && cards[i] == cards[i - 1]) {
// 重复牌,不是有效顺子
return false;
}
// 检查是否出现A和2之间的间隔
if (cards[i] == 14 && i + 1 < start + 5 && cards[i + 1] == 2) {
// A可以视为序列的开始,但与下一张牌之间的间隔必须为3(14-1=13, 13-10=3)
gap = 3;
} else if (cards[i] == 2 && i > start && cards[i - 1] == 14) {
// 前面是A,这里是2,忽略前面的gap检查
gap = 0;
} else if (i > start) {
// 检查间隙是否为1(除了A后面紧跟2的情况)
if (cards[i] - cards[i - 1] != 1 + gap) {
return false;
}
gap = 0; // 重置gap
}
}
return true;
}
private static void printSequence(List<Integer> sequence) {
// 将整数列表转换为字符串并输出
System.out.println(sequence.stream().map(PokerGame::convertValueToString).collect(Collectors.joining(" ")));
}
private static String convertValueToString(int value) {
switch (value) {
case 2: return "2";
case 3: return "3";
case 4: return "4";
case 5: return "5";
case 6: return "6";
case 7: return "7";
case 8: return "8";
case 9: return "9";
case 10: return "10";
case 11: return "J";
case 12: return "Q";
case 13: return "K";
case 14: return "A";
default: throw new IllegalArgumentException("Invalid value: " + value);
}
}
public static void main(String[] args) {
String input = "2 9 J 2 3 4 K A 7 8 A 5 6";
findValidSequences(input);
}
五、代码扩展解析
代码主要逻辑:
-
输入验证与转换:
- 检查输入是否有效(非空且包含13张牌)。
- 将输入的字符串转换为整数数组,并排序。
-
查找顺子:
- 从排序后的数组的第一个元素开始,遍历所有可能的5张牌组合。
- 对于每个组合,检查是否为有效顺子(连续递增且不包含2)。
- 如果是有效顺子,则将其添加到结果列表中。
-
输出顺子:
- 如果找到至少一个顺子,则输出每个顺子。
- 如果没有找到顺子,则输出
NO
。
关键函数解析:
parseCardValue(String cardStr)
:将扑克牌字符串转换为对应的整数值。findSequences(int[] sortedCards)
:查找并返回所有有效的顺子列表。isSequence(int[] cards, int start)
:检查从指定位置开始的5张牌是否为有效顺子。printSequence(List<Integer> sequence)
:将整数列表表示的顺子转换为字符串并输出。
运行示例解析:
输入:2 9 J 2 3 4 K A 7 8 A 5 6
步骤:
-
输入验证与转换:
- 输入有效,包含13张牌。
- 转换为整数数组并排序:
[2, 2, 3, 4, 5, 6, 7, 8, 9, 11, 13, 14, 14]
(注意:J=11, K=13, A=14)。
-
字符串转换为整数数组:
- 将输入字符串按空格分割成数组
splitCards
,然后遍历这个数组,将每张牌转换为对应的整数值,存储在cardValues
数组中。转换规则是:2-9
直接映射,10
映射为10
,J
映射为11
,Q
映射为12
,K
映射为13
,A
映射为14
。 - 转换后的
cardValues
数组为[2, 9, 11, 2, 3, 4, 13, 14, 7, 8, 14, 5, 6]
。
- 将输入字符串按空格分割成数组
-
排序:
- 对
cardValues
数组进行排序,得到[2, 2, 3, 4, 5, 6, 7, 8, 9, 11, 13, 14, 14]
。
- 对
-
查找有效顺子:
- 调用
findSequences
方法,从排序后的数组中寻找所有有效的顺子。 - 遍历数组,从每个可能的起点开始,检查接下来的5张牌是否构成有效顺子。
- 在这个例子中,有效的顺子有
[2, 3, 4, 5, 6]
和[5, 6, 7, 8, 9]
,但由于2
重复出现,它不能作为顺子的开始,所以只有[5, 6, 7, 8, 9]
是有效的。 - A(14)可以视为序列的开始或结束,但在这种情况下,它并不能帮助形成新的有效顺子,因为剩下的牌不足以构成另一个顺子。
- 从数组的第一个元素开始遍历。
- 在位置0处,发现
[2, 2, 3, 4, 5]
,由于包含2,不是有效顺子。 - 在位置2处,发现
[3, 4, 5, 6, 7]
,是有效顺子,添加到结果列表。 - 后续组合中未发现其他有效顺子。
- 调用
-
输出顺子:
- 输出找到的有效顺子:
3 4 5 6 7
。
- 输出找到的有效顺子:
注意:在实际代码中,由于题目要求只输出第一个找到的顺子(按第一张牌大小排序后的第一个),因此即使存在多个顺子,也只输出一个。但在这个示例中,为了说明逻辑,我们假设代码会输出所有找到的有效顺子(实际上,根据题目要求,需要对代码进行适当修改以只输出第一个)。然而,在这个特定的输入示例中,只有一个有效顺子。
最终输出:
3 4 5 6 7
让我们详细解析一下提供的PokerGame
类的运行示例,特别是针对main
方法中给定的输入字符串"2 9 J 2 3 4 K A 7 8 A 5 6"
。
六、注意
- 输入中有两张
2
和两张A
(14),但只能使用其中的一部分来形成有效顺子。 - A(14)可以作为顺子的开始(例如,在
A 2 3 4 5
中),也可以作为顺子的结束(例如,在10 J Q K A
中),但不能同时作为开始和结束(例如,A 2 3 4 A
不是有效顺子)。 - 在这个例子中,
A
并没有帮助形成额外的顺子,因为剩余的牌不足以形成另一个有效的连续序列。