一、题目描述
斗地主起源于湖北十堰房县,据说是一位叫吴修全的年轻人根据当地流行的扑克玩法“跑得快”改编的,如今已风靡整个中国,并流行于互联网上。
牌型:单顺,又称顺子,最少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+2张牌)
二、输入输出描述
输入描述
- 第一行:当前手中的牌(字符串,用
-分隔) - 第二行:已经出过的牌(字符串,用
-分隔)
输出描述
- 最长的顺子序列(按从小到大顺序,用
-分隔); - 若无合法顺子,则输出NO-CHAIN。
三、示例
| 输入 |
3-3-3-3-4-4-5-5-6-7-8-9-10-J-Q-K-A |
| 输出 | 9-10-J-Q-K-A |
| 说明 |
四、解题思路
1. 核心思想
采用数量统计 + 连续遍历验证的策略:
- 数量统计:先通过 “总牌数 - 手中牌 - 已出牌” 计算剩余牌的数量,明确哪些牌可用于组顺子;
- 连续遍历:从顺子的所有可能起始位置(3 到 10)出发,向右遍历连续牌面,验证是否能组成≥5 张的顺子,最终保留最长的那个。
2. 问题本质分析
这个问题的核心是 **“找连续序列的最长有效子串”**:
- 顺子的本质是 “牌面值连续且每张牌至少有 1 张剩余”;
- 顺子的约束条件:① 仅包含 3、4、5、6、7、8、9、10、J、Q、K、A(索引 3-14);② 长度≥5;③ 找最长的;
- 非顺子牌(2、B、C)无需参与遍历,可直接排除,减少计算量。
3. 核心逻辑
- 映射初始化:建立 “牌面值 - 索引” 双向映射,方便快速操作剩余牌数数组;
- 剩余牌计算:通过总牌数减去手中牌、已出牌,得到每张牌的剩余数量;
- 顺子查找:
- 遍历所有可能的顺子起始位置(3 到 10,因顺子至少 5 张,起始超过 10 则无法凑够 5 张);
- 从起始位置向右遍历连续牌面,收集可组成顺子的牌;
- 若遍历中断(某牌无剩余),验证当前顺子是否符合条件,更新最长顺子;
- 结果返回:返回最长顺子,无则返回
NO-CHAIN。
4. 步骤拆解
- 输入处理:读取手中牌、已出牌,按
-分割为数组; - 映射与初始化:
- 构建牌面值↔索引的双向映射;
- 初始化 count 数组为所有牌的总数量;
- 计算剩余牌数:
- 遍历手中牌,对应 count 数组位置数量 - 1;
- 遍历已出牌,对应 count 数组位置数量 - 1;
- 查找最长顺子:
- 初始化答案为
NO-CHAIN,最长长度为 0; - 遍历顺子起始位置
l(3 到 10):- 从
l开始向右遍历r(到 15 为止),若count[r]≥1则加入当前顺子; - 若
count[r]=0,则验证当前顺子长度≥5 且≥最长长度,若是则更新答案; - 中断当前遍历,左边界跳转到
r,继续下一个起始位置;
- 从
- 左边界
l自增,重复上述步骤;
- 初始化答案为
- 输出结果:返回最长顺子(或
NO-CHAIN)。
五、代码实现
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Scanner;
import java.util.StringJoiner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String[] my = sc.nextLine().split("-");
String[] used = sc.nextLine().split("-");
System.out.println(getResult(my, used));
}
public static String getResult(String[] my, String[] used) {
// 牌面值 映射为 count列表索引值
HashMap<String, Integer> mapToV = new HashMap<>();
mapToV.put("3", 3);
mapToV.put("4", 4);
mapToV.put("5", 5);
mapToV.put("6", 6);
mapToV.put("7", 7);
mapToV.put("8", 8);
mapToV.put("9", 9);
mapToV.put("10", 10);
mapToV.put("J", 11);
mapToV.put("Q", 12);
mapToV.put("K", 13);
mapToV.put("A", 14);
mapToV.put("2", 16);
mapToV.put("B", 17);
mapToV.put("C", 18);
// count每个索引值对应一个牌面值,count元素值就是对应牌面的数量
// 牌面值 3 4 5 6 7 8 9 10 J Q K A 2 B C
// 索引值 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
int[] count = {0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 4, 1, 1};
// count列表索引值 隐射为 牌面值
HashMap<Integer, String> mapToK = new HashMap<>();
mapToK.put(3, "3");
mapToK.put(4, "4");
mapToK.put(5, "5");
mapToK.put(6, "6");
mapToK.put(7, "7");
mapToK.put(8, "8");
mapToK.put(9, "9");
mapToK.put(10, "10");
mapToK.put(11, "J");
mapToK.put(12, "Q");
mapToK.put(13, "K");
mapToK.put(14, "A");
mapToK.put(16, "2");
mapToK.put(17, "B");
mapToK.put(18, "C");
// 总牌数 减去 自己手中牌数
for (String k : my) {
count[mapToV.get(k)] -= 1;
}
// 总牌数 减去 已打出去的牌数
for (String k : used) {
count[mapToV.get(k)] -= 1;
}
String ans = "NO-CHAIN";
int maxLen = 0;
// l为顺子的左边界,[3,10],即顺子的左边界值最少是count索引3,最多是count索引10
int l = 3;
while (l <= 10) {
ArrayList<String> tmp = new ArrayList<>();
StringJoiner sj = new StringJoiner("-");
for (int r = l; r < 16; r++) {
// 如果对应牌数>=1,则可以组顺子
if (count[r] >= 1) {
tmp.add(mapToK.get(r));
sj.add(mapToK.get(r));
} else {
// 如果对应牌数 == 0,则顺子中断
// 顺子必须大于五张牌,且总是记录最长,遇到长度相同的,记录后面发现的顺子
if (tmp.size() >= 5 && tmp.size() >= maxLen) {
maxLen = tmp.size();
ans = sj.toString();
}
// 顺子中断处+1,即为下一次顺子的起始位置
l = r;
break;
}
}
l++;
}
return ans;
}
}

534

被折叠的 条评论
为什么被折叠?



