【算法】基于滑动窗口的区间问题求解算法与实例:华为OD机考双机位A卷 - 最长的顺子

题目描述

斗地主起源于湖北十堰房县,据说是一位叫吴修全的年轻人根据当地流行的扑克玩法 "跑得快" 改编的,如今已风靡整个中国,并流行于互联网上。

牌型:单顺,又称顺子,最少 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 张牌)

输入:

  1. 手上有的牌
  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;
}
相关推荐
嵌入式进阶行者21 小时前
【算法】用三种解法解决字符串替换问题的实例:华为OD机考双机位A卷 - 密码解密
c++·算法·华为od
罗湖老棍子21 小时前
信使(msner)(信息学奥赛一本通- P1376)四种做法
算法·图论·dijkstra·spfa·floyd·最短路算法
No0d1es21 小时前
2025年12月 GESP CCF编程能力等级认证Python三级真题
开发语言·php
生成论实验室1 天前
生成论之基:“阴阳”作为元规则的重构与证成——基于《易经》与《道德经》的古典重诠与现代显象
人工智能·科技·神经网络·算法·架构
lalala_lulu1 天前
什么是事务,事务有什么特性?
java·开发语言·数据库
CCPC不拿奖不改名1 天前
python基础:python语言中的函数与模块+面试习题
开发语言·python·面试·职场和发展·蓝桥杯
毕设源码-朱学姐1 天前
【开题答辩全过程】以 基于Python语言的疫情数据可视化系统为例,包含答辩的问题和答案
开发语言·python·信息可视化
哥只是传说中的小白1 天前
Nano Banana Pro高并发接入Grsai Api实战!0.09/张无限批量生成(附接入实战+开源工具)
开发语言·数据库·ai作画·开源·aigc·php·api
啊董dong1 天前
noi-2026年1月07号作业
数据结构·c++·算法·noi