题目描述
斗地主起源于湖北十堰房县,据说是一位叫吴修全的年轻人根据当地流行的扑克玩法 "跑得快" 改编的,如今已风靡整个中国,并流行于互联网上。
牌型:单顺,又称顺子,最少 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。
输入描述
输入的第一行为当前手中的牌
输入的第二行为已经出过的牌
输出描述
示例
输入
cpp
3-3-3-3-4-4-5-5-6-7-8-9-10-J-Q-K-A
4-5-6-7-8-8-8
输出
cpp
9-10-J-Q-K-A
一、解题思路分析
1.核心算法设计
程序采用滑动窗口算法在有序的牌面空间中寻找最长连续可用序列,这是典型的区间查找问题。
2. 数据建模关键点
牌面映射策略
cpp
数字2-10 → 对应数字
J → 11, Q → 12, K → 13, A → 14
大王 → 0, 小王 → 1
这种映射巧妙地将牌面转换为可比较的数字,同时将大小王隔离在顺子范围外。
牌库初始化
-
标准牌每种4张(A、2-10、J、Q、K)
-
大王小王各1张
-
总牌数:13×4 + 2 = 54张
3. 算法流程分解
步骤1:数据解析与统计
cpp
// 解析输入字符串,统计每种牌的数量
void indexMap(map<int, int>& index_map, string str)
使用字符串流分割,根据首字符识别牌型并计数。
步骤2:剩余牌计算
cpp
剩余牌 = 总牌库 - 手牌 - 已出牌
通过遍历所有牌面索引(0-14),计算每种牌的剩余数量。
步骤3:滑动窗口查找最长顺子
cpp
int left = 3, right = 3; // 顺子从3开始
int count = 0; // 当前连续长度
窗口规则:
-
右指针
right向右移动,遇到有剩余牌则计数增加 -
当计数≥5时更新最长顺子记录
-
遇到断牌(剩余为0)时重置计数和左指针
步骤4:结果输出
-
找到≥5的顺子:将数字索引转换回牌面输出
-
未找到:输出"NO-CHAIN"
4. 算法复杂度分析
-
时间复杂度:O(n),n为牌面种类数(15种)
-
空间复杂度:O(1),使用固定大小的映射表
二、C++实现
cpp
#include <iostream>
#include <vector>
#include <algorithm>
#include <sstream>
#include <map>
using namespace std;
// 解析牌字符串,统计每种牌的数量
void indexMap(map<int, int>& index_map, string str) {
stringstream ss(str);
string token;
while (getline(ss, token, '-')) {
if (isdigit(token[0])) { // 数字牌2-10
index_map[stoi(token)]++;
} else if (token[0] == 'A') { // A牌
index_map[14]++;
} else if (token[0] == 'J') { // J牌
index_map[11]++;
} else if (token[0] == 'Q') { // Q牌
index_map[12]++;
} else if (token[0] == 'K') { // K牌
index_map[13]++;
} else if (token[0] == 'B') { // 大王
index_map[0]++;
} else if (token[0] == 'C') { // 小王
index_map[1]++;
}
}
}
int main() {
string str;
// 读取手牌并统计
getline(cin, str);
map<int, int> hand_to_index;
indexMap(hand_to_index, str);
// 读取已出牌并统计
map<int, int> out_to_index;
str.clear();
getline(cin, str);
indexMap(out_to_index, str);
// 初始化总牌库(4副牌+2王牌)
map<int, int> all_to_index;
string all_str = "A-2-3-4-5-6-7-8-9-10-J-Q-K";
for (int i = 0; i < 4; i++) {
indexMap(all_to_index, all_str);
}
all_to_index[0] = 1; // 大王
all_to_index[1] = 1; // 小王
// 计算剩余牌
map<int, int> leave_to_index;
for (int i = 0; i < 15; i++) {
int tmp = all_to_index[i] - hand_to_index[i] - out_to_index[i];
if (tmp > 0) {
leave_to_index[i] = tmp;
}
}
// 查找最长顺子
int longest = 0; // 最长顺子长度
int left = 3; // 顺子起始位置
int right = 3; // 顺子结束位置
int count = 0; // 当前连续牌数
pair<int, int> bestResult; // 最佳顺子范围
// 滑动窗口算法
while (right < 15) {
if (leave_to_index[right] > 0) { // 有牌可用
count++;
if (count == 1) {
left = right; // 记录起点
}
// 更新最长顺子
if (count >= 5 && count >= longest) {
longest = count;
bestResult = {left, right};
}
right++;
} else { // 牌断了
right++;
count = 0;
}
}
// 输出结果
if (longest >= 5) {
// 将数字索引转回牌面输出
for (int i = bestResult.first; i <= bestResult.second; i++) {
if (i <= 10) {
cout << i;
} else if (i == 11) {
cout << "J";
} else if (i == 12) {
cout << "Q";
} else if (i == 13) {
cout << "K";
} else if (i == 14) {
cout << "A";
}
if (i < bestResult.second) {
cout << "-";
}
}
cout << endl;
} else {
cout << "NO-CHAIN" << endl;
}
return 0;
}