华为OD机试真题——最长的顺子(2025A卷:100分)Java/python/JavaScript/C++/C语言/GO六种最佳实现

news2025/5/18 8:52:56

在这里插入图片描述

2025 A卷 100分 题型

本文涵盖详细的问题分析、解题思路、代码实现、代码详解、测试用例以及综合分析;
并提供Java、python、JavaScript、C++、C语言、GO六种语言的最佳实现方式!

本文收录于专栏:《2025华为OD真题目录+全流程解析/备考攻略/经验分享》

华为OD机试真题《最长的顺子》:


目录

    • 题目名称:最长的顺子
      • 题目描述
    • Java
      • 问题分析
      • 解题思路
      • 代码实现
      • 代码详细解析
      • 示例测试
      • 综合分析
    • python
      • 问题分析
      • 解题思路
      • 代码实现
      • 代码详细解析
      • 示例测试
      • 综合分析
    • JavaScript
      • 问题分析
      • 解题思路
      • 代码实现
      • 代码详细解析
      • 示例测试
      • 综合分析
    • C++
      • 问题分析
      • 解题思路
      • 代码实现
      • 代码详细解析
      • 示例测试
        • 示例1输入:
        • 示例2输入(无顺子):
        • 示例3输入(完整顺子):
      • 综合分析
    • C语言
      • 问题分析
      • 解题思路
      • 代码实现
      • 代码详细解析
      • 示例测试
        • 示例1输入:
        • 示例2输入(无顺子):
        • 示例3输入(最长顺子):
      • 综合分析
    • GO
      • 问题分析
      • 解题思路
      • 代码实现
      • 代码详细解析
      • 示例测试
        • 示例1输入:
        • 示例2输入(无顺子):
        • 示例3输入(最长顺子):
      • 综合分析
    • 更多内容:


题目名称:最长的顺子


知识点:字符串、动态规划/滑动窗口、逻辑处理
时间限制:1秒
空间限制:256MB
语言限制:不限


题目描述

输入当前手牌和已出牌,计算对手可能组成的最长顺子(连续5-12张牌,不含2和大小王)。若存在多个最长顺子,输出牌面最大的那个,否则返回"NO-CHAIN"。

规则说明

  1. 顺子定义:连续递增的牌序列,范围从3到A(3<4<5<6<7<8<9<10<J<Q<K<A),不含2、B(小王)、C(大王)。
  2. 输入格式
    • 第一行为当前手牌(如 3-3-4-5-A
    • 第二行为已出牌(如 A-4-5-6-7
  3. 输出格式:最长顺子,若长度相同则输出牌面最大的(如 9-10-J-Q-K-A 优于 5-6-7-8-9)。
  4. 数据约束:每张牌初始有4张(除大小王),总牌数为54张。

输入示例

3-3-4-4-5-A-5-6-2-8-3-9-10-Q-7-K-J-10-B  
A-4-5-8-8-10-C-6-7-8  

输出示例

9-10-J-Q-K-A  

Java

问题分析

我们需要找到对手可能组成的最长顺子。顺子由3到A的连续牌组成,不含2和大小王。输出最长顺子,若存在多个相同长度的,选择牌面最大的那个。若无法组成,返回"NO-CHAIN"。

解题思路

  1. 统计剩余牌数:根据手牌和已出牌,计算每个有效牌的剩余数量。
  2. 滑动窗口遍历:从每个可能的起始牌开始,尽可能向右扩展窗口,直到遇到剩余不足的牌。
  3. 选择最优结果:记录最长窗口,若长度相同则选择末尾最大的窗口。

代码实现

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        // 读取输入的两行并分割成牌列表
        String handInput = scanner.nextLine().trim();
        String playedInput = scanner.nextLine().trim();
        List<String> allCards = new ArrayList<>();

        if (!handInput.isEmpty()) {
            allCards.addAll(Arrays.asList(handInput.split("-")));
        }
        if (!playedInput.isEmpty()) {
            allCards.addAll(Arrays.asList(playedInput.split("-")));
        }

        // 有效牌的顺序和集合
        String[] order = {"3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"};
        Set<String> validCards = new HashSet<>(Arrays.asList(order));

        // 统计每个牌的出现次数
        Map<String, Integer> countMap = new HashMap<>();
        for (String card : allCards) {
            if (validCards.contains(card)) {
                countMap.put(card, countMap.getOrDefault(card, 0) + 1);
            }
        }

        // 计算每个牌的剩余次数
        int[] remain = new int[order.length];
        for (int i = 0; i < order.length; i++) {
            String card = order[i];
            int used = countMap.getOrDefault(card, 0);
            remain[i] = 4 - used; // 每张牌最多4张
        }

        // 滑动窗口遍历寻找最长顺子
        int maxLen = 0;
        int bestStart = 0;
        int bestEnd = 0;

        for (int i = 0; i < order.length; i++) {
            if (remain[i] <= 0) continue; // 起始牌必须可用

            int currentEnd = i;
            // 向右扩展窗口,直到遇到不可用的牌
            for (int j = i; j < order.length; j++) {
                if (remain[j] <= 0) break;
                currentEnd = j;
            }

            int currentLen = currentEnd - i + 1;
            // 更新最长且牌面最大的顺子
            if (currentLen >= 5) {
                if (currentLen > maxLen || (currentLen == maxLen && currentEnd > bestEnd)) {
                    maxLen = currentLen;
                    bestStart = i;
                    bestEnd = currentEnd;
                }
            }
        }

        // 输出结果
        if (maxLen >= 5) {
            List<String> result = new ArrayList<>();
            for (int i = bestStart; i <= bestEnd; i++) {
                result.add(order[i]);
            }
            System.out.println(String.join("-", result));
        } else {
            System.out.println("NO-CHAIN");
        }
    }
}

代码详细解析

  1. 输入处理:读取手牌和已出牌,分割为列表。
  2. 有效牌集合:定义合法的顺子牌和顺序。
  3. 统计次数:遍历所有牌,统计每个有效牌的出现次数。
  4. 剩余计算:每种牌最多4张,剩余次数为4 - 已用次数
  5. 滑动窗口
    • 外层遍历每个起始点i,若该牌剩余可用则继续。
    • 内层扩展窗口j,直到遇到剩余为0的牌,记录窗口结束位置。
    • 更新最长且最大的顺子。
  6. 结果输出:根据窗口生成顺子字符串或输出“NO-CHAIN”。

示例测试

示例1输入

3-3-4-4-5-A-5-6-2-8-3-9-10-Q-7-K-J-10-B  
A-4-5-8-8-10-C-6-7-8  

输出9-10-J-Q-K-A
解析:剩余牌中9到A的连续序列最长且牌面最大。

示例2输入(无法形成顺子):

3-3-3-3  
4-4-4-4  

输出NO-CHAIN

示例3输入(最长顺子):

3-4-5-6-7-8-9-10-J-Q-K-A  
(空已出牌)

输出3-4-5-6-7-8-9-10-J-Q-K-A

综合分析

  1. 时间复杂度:O(n²),n为有效牌数量(12),双重循环遍历。
  2. 空间复杂度:O(n),存储剩余次数和牌顺序。
  3. 优势:滑动窗口确保找到最优解,处理高效。
  4. 适用场景:适用于题目约束的小规模输入,快速找到最长顺子。

python

问题分析

我们需要找到对手可能组成的最长顺子。顺子由3到A的连续牌组成,不含2和大小王。输出最长顺子,若存在多个相同长度的,选择牌面最大的那个。若无法组成,返回"NO-CHAIN"。


解题思路

  1. 输入处理:将手牌和已出牌合并,统计每张牌的出现次数。
  2. 剩余牌计算:每张牌最多4张,减去已出现的次数得到剩余数量。
  3. 滑动窗口遍历:从每个可能的起点出发,寻找最长的连续可用牌序列。
  4. 最优顺子选择:记录最长且最大的顺子。

代码实现

def main():
    # 读取输入的两行数据
    hand = input().strip().split('-') if input().strip() != '' else []
    played = input().strip().split('-') if input().strip() != '' else []
    all_cards = hand + played

    # 定义有效牌的顺序(从3到A)
    order = ["3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"]
    valid_cards = set(order)  # 用于快速判断是否为有效牌

    # 统计每张牌的使用次数
    count = {}
    for card in all_cards:
        if card in valid_cards:
            count[card] = count.get(card, 0) + 1

    # 计算每张牌的剩余数量(总牌数为4)
    remain = {card: 4 - count.get(card, 0) for card in order}

    max_length = 0       # 最长顺子的长度
    best_start = 0       # 最长顺子的起始索引
    best_end = -1        # 最长顺子的结束索引

    # 遍历所有可能的起始点
    for i in range(len(order)):
        # 如果当前牌不可用,跳过
        if remain[order[i]] <= 0:
            continue

        current_end = i  # 当前窗口的结束位置
        # 从i出发,尽可能向右扩展窗口
        for j in range(i, len(order)):
            if remain[order[j]] <= 0:
                break    # 遇到不可用的牌,停止扩展
            current_end = j

        # 计算当前窗口的长度
        current_length = current_end - i + 1

        # 更新最长顺子(长度优先,其次比较结束索引)
        if current_length >= 5:
            if (current_length > max_length) or \
               (current_length == max_length and current_end > best_end):
                max_length = current_length
                best_start = i
                best_end = current_end

    # 输出结果
    if max_length >= 5:
        chain = order[best_start : best_end+1]
        print("-".join(chain))
    else:
        print("NO-CHAIN")

if __name__ == "__main__":
    main()

代码详细解析

  1. 输入处理

    • 读取手牌和已出牌,分割为列表(split('-'))。
    • 合并所有牌到 all_cards 列表。
  2. 有效牌定义

    order = ["3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"]
    
    • 明确顺子的合法牌面和顺序。
  3. 统计剩余牌数

    remain = {card: 4 - count.get(card, 0) for card in order}
    
    • 计算每张牌剩余可用次数(初始为4,减去已出现的次数)。
  4. 滑动窗口遍历

    • 外层循环遍历每个可能的起始点 i
    • 内层循环从 i 出发向右扩展窗口,直到遇到不可用的牌。
    • 记录当前窗口的结束位置 current_end
  5. 最优顺子选择

    • 如果当前窗口长度大于历史最大值,更新结果。
    • 如果长度相同,但结束索引更大(即牌面更大),也更新结果。
  6. 结果输出

    • 根据记录的起始和结束索引生成顺子字符串。
    • 若没有合法顺子,输出 NO-CHAIN

示例测试

示例1输入:

3-3-4-4-5-A-5-6-2-8-3-9-10-Q-7-K-J-10-B  
A-4-5-8-8-10-C-6-7-8

输出

9-10-J-Q-K-A

解析

  • 剩余牌中 9,10,J,Q,K,A 连续且全部可用,长度为6,是唯一最长顺子。

示例2输入(无顺子):

3-3-3-3
4-4-4-4

输出

NO-CHAIN

解析

  • 所有牌的剩余次数为0,无法组成顺子。

示例3输入(最长顺子):

3-4-5-6-7-8-9-10-J-Q-K-A
(空已出牌)

输出

3-4-5-6-7-8-9-10-J-Q-K-A

解析

  • 所有牌均未被使用,可以组成完整的12张顺子。

综合分析

  1. 时间复杂度

    • 滑动窗口遍历:外层循环12次,内层最多遍历12次,复杂度为 (O(n^2)),其中 (n=12)。
    • 总复杂度为 (O(144)),完全满足题目约束。
  2. 空间复杂度

    • 存储牌的顺序和剩余次数,空间复杂度为 (O(n)),其中 (n=12)。
  3. 优势

    • 严格保证最优解:通过滑动窗口直接遍历所有可能性。
    • 高效剪枝:遇到不可用牌立即停止扩展,减少计算量。
  4. 适用场景

    • 题目明确要求牌数 (n < 31),滑动窗口法在小规模数据中表现最佳。

JavaScript

问题分析

我们需要找到对手可能组成的最长顺子。顺子由3到A的连续牌组成,不含2和大小王。输出最长顺子,若存在多个相同长度的,选择牌面最大的那个。若无法组成,返回"NO-CHAIN"。


解题思路

  1. 输入处理:读取手牌和已出牌,合并并分割为数组。
  2. 统计次数:计算每个有效牌的出现次数。
  3. 剩余计算:每张牌最多4张,剩余次数为4 - 已用次数
  4. 滑动窗口遍历:从每个可能的起点向右扩展,找到最长连续可用序列。
  5. 结果选择:记录最长且最大的顺子。

代码实现

const readline = require('readline');

const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
    terminal: false
});

let lines = [];
rl.on('line', (line) => {
    lines.push(line.trim());
}).on('close', () => {
    // 解析输入的两行数据
    const hand = lines[0] ? lines[0].split('-') : [];
    const played = lines[1] ? lines[1].split('-') : [];
    const allCards = [...hand, ...played];

    // 定义有效牌的顺序和集合
    const order = ["3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"];
    const validCards = new Set(order);

    // 统计每张牌的出现次数
    const count = {};
    allCards.forEach(card => {
        if (validCards.has(card)) {
            count[card] = (count[card] || 0) + 1;
        }
    });

    // 计算剩余次数(4 - 已用次数)
    const remain = {};
    order.forEach(card => {
        remain[card] = 4 - (count[card] || 0);
    });

    let maxLength = 0;    // 最长顺子的长度
    let bestStart = 0;    // 最长顺子的起始索引
    let bestEnd = -1;     // 最长顺子的结束索引

    // 滑动窗口遍历所有可能的起始点
    for (let i = 0; i < order.length; i++) {
        const startCard = order[i];
        if (remain[startCard] <= 0) continue; // 起始牌不可用则跳过

        let currentEnd = i;
        // 向右扩展窗口直到遇到不可用的牌
        for (let j = i; j < order.length; j++) {
            const currentCard = order[j];
            if (remain[currentCard] <= 0) break;
            currentEnd = j;
        }

        const currentLength = currentEnd - i + 1;
        // 更新最优解(长度优先,其次比较末尾牌大小)
        if (currentLength >= 5) {
            if (
                currentLength > maxLength ||
                (currentLength === maxLength && currentEnd > bestEnd)
            ) {
                maxLength = currentLength;
                bestStart = i;
                bestEnd = currentEnd;
            }
        }
    }

    // 输出结果
    if (maxLength >= 5) {
        const bestChain = order.slice(bestStart, bestEnd + 1).join('-');
        console.log(bestChain);
    } else {
        console.log("NO-CHAIN");
    }
});

代码详细解析

  1. 输入处理

    • 使用 readline 读取输入,存储到 lines 数组。
    • 将手牌和已出牌分割为数组,合并到 allCards
  2. 有效牌定义

    const order = ["3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"];
    const validCards = new Set(order);
    
    • 明确合法的顺子牌及其顺序。
  3. 统计次数

    allCards.forEach(card => {
        if (validCards.has(card)) {
            count[card] = (count[card] || 0) + 1;
        }
    });
    
    • 遍历所有牌,统计有效牌的出现次数。
  4. 剩余计算

    order.forEach(card => {
        remain[card] = 4 - (count[card] || 0);
    });
    
    • 计算每个牌的剩余次数(总次数为4)。
  5. 滑动窗口遍历

    • 外层循环遍历每个可能的起始点 i
    • 内层循环从 i 出发向右扩展窗口,直到遇到剩余为0的牌。
    • 记录窗口的结束位置 currentEnd
  6. 结果选择

    • 如果当前窗口长度大于历史最大值,更新最优解。
    • 如果长度相同但末尾牌更大(即 currentEnd 更大),也更新最优解。
  7. 输出结果

    • 生成顺子字符串(如 9-10-J-Q-K-A)或输出 NO-CHAIN

示例测试

示例1输入:

3-3-4-4-5-A-5-6-2-8-3-9-10-Q-7-K-J-10-B  
A-4-5-8-8-10-C-6-7-8

输出

9-10-J-Q-K-A

解析

  • 剩余牌中 9,10,J,Q,K,A 连续可用,形成最长顺子。

示例2输入(无顺子):

3-3-3-3
4-4-4-4

输出

NO-CHAIN

解析

  • 所有牌剩余次数为0,无法组成顺子。

示例3输入(最长顺子):

3-4-5-6-7-8-9-10-J-Q-K-A
(空已出牌)

输出

3-4-5-6-7-8-9-10-J-Q-K-A

综合分析

  1. 时间复杂度

    • 滑动窗口遍历:外层循环12次,内层最多12次,复杂度为 (O(n^2)),其中 (n=12)。
    • 总复杂度为 (O(144)),完全满足题目约束。
  2. 空间复杂度

    • 存储牌的顺序和剩余次数,空间复杂度为 (O(n)),其中 (n=12)。
  3. 优势

    • 严格保证最优解:滑动窗口直接遍历所有可能性。
    • 高效剪枝:遇到不可用牌立即停止扩展,减少计算量。
  4. 适用场景

    • 题目明确要求牌数 (n < 31),滑动窗口法在小规模数据中表现最佳。

C++

问题分析

我们需要找出对手可能组成的最长顺子。顺子由3到A的连续牌组成,不含2和大小王。输出最长顺子,若存在多个相同长度的,选择牌面最大的那个。若无法组成,返回"NO-CHAIN"。


解题思路

  1. 输入处理:读取手牌和已出牌,分割成数组。
  2. 统计次数:过滤无效牌,统计每张牌的出现次数。
  3. 剩余计算:每张牌最多4张,剩余次数为 4 - 已用次数
  4. 滑动窗口遍历:从每个可能的起点扩展窗口,找到最长连续可用序列。
  5. 结果生成:记录最长且最大的顺子并输出。

代码实现

#include <iostream>
#include <vector>
#include <string>
#include <unordered_map>
#include <sstream>
#include <unordered_set>

using namespace std;

// 分割字符串函数
vector<string> split(const string &s, char delimiter) {
    vector<string> tokens;
    string token;
    istringstream tokenStream(s);
    while (getline(tokenStream, token, delimiter)) {
        if (!token.empty()) {
            tokens.push_back(token);
        }
    }
    return tokens;
}

// 连接字符串函数
string join(const vector<string> &v, const string &delimiter) {
    if (v.empty()) return "";
    string result = v[0];
    for (size_t i = 1; i < v.size(); ++i) {
        result += delimiter + v[i];
    }
    return result;
}

int main() {
    // 定义有效牌的顺序
    const vector<string> order = {"3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"};
    unordered_set<string> validCards(order.begin(), order.end());

    // 读取输入
    string handInput, playedInput;
    getline(cin, handInput);
    getline(cin, playedInput);

    // 分割输入为牌列表
    vector<string> hand = split(handInput, '-');
    vector<string> played = split(playedInput, '-');
    vector<string> allCards;
    allCards.insert(allCards.end(), hand.begin(), hand.end());
    allCards.insert(allCards.end(), played.begin(), played.end());

    // 统计每张牌的出现次数(仅有效牌)
    unordered_map<string, int> countMap;
    for (const auto &card : allCards) {
        if (validCards.find(card) != validCards.end()) {
            countMap[card]++;
        }
    }

    // 计算剩余次数
    vector<int> remain(order.size(), 4);
    for (int i = 0; i < order.size(); ++i) {
        const string &card = order[i];
        remain[i] = 4 - countMap[card];
    }

    // 滑动窗口遍历寻找最长顺子
    int maxLen = 0;
    int bestStart = 0;
    int bestEnd = -1;

    for (int i = 0; i < order.size(); ++i) {
        if (remain[i] <= 0) continue; // 起始牌不可用则跳过

        int currentEnd = i;
        for (int j = i; j < order.size(); ++j) {
            if (remain[j] <= 0) break; // 遇到不可用的牌,停止扩展
            currentEnd = j;             // 更新窗口结束位置
        }

        int currentLen = currentEnd - i + 1;
        // 更新最优解(优先长度,其次末尾牌大小)
        if (currentLen >= 5) {
            if (currentLen > maxLen || (currentLen == maxLen && currentEnd > bestEnd)) {
                maxLen = currentLen;
                bestStart = i;
                bestEnd = currentEnd;
            }
        }
    }

    // 输出结果
    if (maxLen >= 5) {
        vector<string> bestChain;
        for (int i = bestStart; i <= bestEnd; ++i) {
            bestChain.push_back(order[i]);
        }
        cout << join(bestChain, "-") << endl;
    } else {
        cout << "NO-CHAIN" << endl;
    }

    return 0;
}

代码详细解析

  1. 输入处理

    • getline 读取输入的两行数据。
    • split 函数按 - 分割字符串,得到手牌和已出牌列表。
  2. 统计次数

    • 使用 unordered_map 统计有效牌的出现次数,过滤2和大小王。
  3. 剩余计算

    • 数组 remain 存储每个有效牌的剩余次数,初始为4,减去已用次数。
  4. 滑动窗口遍历

    • 外层循环遍历每个起始点 i,若该牌剩余可用则继续。
    • 内层循环向右扩展窗口,直到遇到剩余为0的牌,记录窗口结束位置。
    • 比较当前窗口长度和记录的最大值,更新最优解。
  5. 结果生成

    • 根据记录的起始和结束位置生成顺子字符串,用 join 函数连接。

示例测试

示例1输入:
3-3-4-4-5-A-5-6-2-8-3-9-10-Q-7-K-J-10-B  
A-4-5-8-8-10-C-6-7-8

输出

9-10-J-Q-K-A

解析:剩余牌中 9-10-J-Q-K-A 是连续且最长的。

示例2输入(无顺子):
3-3-3-3
4-4-4-4

输出

NO-CHAIN
示例3输入(完整顺子):
3-4-5-6-7-8-9-10-J-Q-K-A
(空已出牌)

输出

3-4-5-6-7-8-9-10-J-Q-K-A

综合分析

  1. 时间复杂度

    • 滑动窗口遍历:外层循环12次,内层最多12次,复杂度为 (O(n^2)),其中 (n=12)。
    • 总时间 (O(144)),完全满足题目时间限制。
  2. 空间复杂度

    • 存储牌的顺序和剩余次数,空间复杂度为 (O(n)),其中 (n=12)。
  3. 优势

    • 严格最优解:滑动窗口遍历所有可能性,保证找到最长且最大的顺子。
    • 高效剪枝:遇到不可用牌立即停止扩展,减少无效计算。
  4. 适用场景

    • 题目约束的输入规模(牌数 < 31),高效处理中小规模数据。

C语言

问题分析

我们需要找到对手可能组成的最长顺子,顺子由3到A的连续牌组成,不含2和大小王。输出最长顺子,若存在多个相同长度的,选择牌面最大的那个。若无法组成,返回"NO-CHAIN"。


解题思路

  1. 输入处理:读取手牌和已出牌,分割为数组。
  2. 统计剩余牌数:计算每个有效牌的剩余数量(总4张减去已出现次数)。
  3. 滑动窗口遍历:从每个可能的起点向右扩展,寻找最长连续可用序列。
  4. 结果选择:记录最长且末尾最大的顺子。

代码实现

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define MAX_CARDS 1000  // 最大输入牌数
#define ORDER_SIZE 12   // 有效牌种类数

// 有效牌顺序:3,4,5,6,7,8,9,10,J,Q,K,A
const char *order[] = {"3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"};

// 分割字符串为数组,返回分割后的元素数量
int split_string(char *str, char result[][4], const char *delim) {
    int count = 0;
    char *token = strtok(str, delim);
    while (token != NULL) {
        strcpy(result[count], token);
        count++;
        token = strtok(NULL, delim);
    }
    return count;
}

// 根据牌名查找其索引,返回-1表示无效牌
int find_index(const char *card) {
    for (int i = 0; i < ORDER_SIZE; i++) {
        if (strcmp(card, order[i]) == 0) return i;
    }
    return -1;
}

int main() {
    char hand[1000], played[1000];
    char cards_hand[MAX_CARDS][4], cards_played[MAX_CARDS][4];
    int hand_count = 0, played_count = 0;

    // 读取输入并分割
    fgets(hand, sizeof(hand), stdin);
    hand[strcspn(hand, "\n")] = '\0';  // 去除换行符
    fgets(played, sizeof(played), stdin);
    played[strcspn(played, "\n")] = '\0';

    hand_count = split_string(hand, cards_hand, "-");
    played_count = split_string(played, cards_played, "-");

    // 统计所有牌的出现次数
    int count[ORDER_SIZE] = {0};
    for (int i = 0; i < hand_count; i++) {
        int idx = find_index(cards_hand[i]);
        if (idx != -1) count[idx]++;
    }
    for (int i = 0; i < played_count; i++) {
        int idx = find_index(cards_played[i]);
        if (idx != -1) count[idx]++;
    }

    // 计算剩余次数
    int remain[ORDER_SIZE];
    for (int i = 0; i < ORDER_SIZE; i++) {
        remain[i] = 4 - count[i];
    }

    // 滑动窗口遍历寻找最长顺子
    int max_len = 0, best_start = 0, best_end = -1;
    for (int i = 0; i < ORDER_SIZE; i++) {
        if (remain[i] <= 0) continue;  // 起始牌不可用

        int current_end = i;
        for (int j = i; j < ORDER_SIZE; j++) {
            if (remain[j] <= 0) break;
            current_end = j;
        }

        int current_len = current_end - i + 1;
        if (current_len >= 5) {
            if (current_len > max_len || (current_len == max_len && current_end > best_end)) {
                max_len = current_len;
                best_start = i;
                best_end = current_end;
            }
        }
    }

    // 输出结果
    if (max_len >= 5) {
        for (int i = best_start; i <= best_end; i++) {
            printf("%s", order[i]);
            if (i != best_end) printf("-");
        }
        printf("\n");
    } else {
        printf("NO-CHAIN\n");
    }

    return 0;
}

代码详细解析

  1. 定义常量

    • MAX_CARDS:输入牌的最大数量。
    • ORDER_SIZE:有效牌的种类数(3到A共12种)。
  2. 有效牌顺序

    const char *order[] = {"3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"};
    
    • 明确有效牌的顺序,方便后续处理。
  3. 分割字符串

    int split_string(char *str, char result[][4], const char *delim) {
        // 使用strtok分割字符串为数组
    }
    
    • 将输入行按“-”分割为单独的牌。
  4. 查找牌索引

    int find_index(const char *card) {
        for (int i = 0; i < ORDER_SIZE; i++) {
            if (strcmp(card, order[i]) == 0) return i;
        }
        return -1;
    }
    
    • 根据牌名查找其在顺序数组中的索引,用于统计次数。
  5. 统计剩余次数

    int remain[ORDER_SIZE];
    for (int i = 0; i < ORDER_SIZE; i++) {
        remain[i] = 4 - count[i];
    }
    
    • 计算每张牌的剩余可用次数。
  6. 滑动窗口遍历

    • 外层循环遍历所有可能的起始点。
    • 内层循环向右扩展窗口,直到遇到不可用牌。
    • 记录最长且最大的窗口。
  7. 输出结果

    • 生成顺子字符串或输出"NO-CHAIN"。

示例测试

示例1输入:
3-3-4-4-5-A-5-6-2-8-3-9-10-Q-7-K-J-10-B  
A-4-5-8-8-10-C-6-7-8

输出

9-10-J-Q-K-A

解析

  • 剩余牌中9到A连续且可用,形成最长顺子。
示例2输入(无顺子):
3-3-3-3
4-4-4-4

输出

NO-CHAIN

解析

  • 所有牌剩余次数为0,无法组成顺子。
示例3输入(最长顺子):
3-4-5-6-7-8-9-10-J-Q-K-A
(空已出牌)

输出

3-4-5-6-7-8-9-10-J-Q-K-A

综合分析

  1. 时间复杂度

    • 输入处理:O(n),n为输入牌数量。
    • 滑动窗口遍历:O(12²) = O(144),高效处理小规模数据。
  2. 空间复杂度

    • 输入存储:O(n),存储分割后的牌。
    • 统计数组:O(12),固定空间消耗。
  3. 优势

    • 严格顺序遍历:保证找到最长且最大的顺子。
    • 高效剪枝:遇到不可用牌立即停止扩展。
  4. 适用场景

    • 输入规模小(题目约束),适合滑动窗口法。

GO

问题分析

我们需要找到对手可能组成的最长顺子。顺子由3到A的连续牌组成,不含2和大小王。输出最长顺子,若存在多个相同长度的,选择牌面最大的那个。若无法组成,返回"NO-CHAIN"。


解题思路

  1. 输入处理:读取手牌和已出牌,分割为数组并过滤无效牌。
  2. 统计剩余牌数:每张牌最多4张,剩余次数为4 - 已用次数
  3. 滑动窗口遍历:从每个可能的起点向右扩展,寻找最长连续可用序列。
  4. 结果选择:记录最长且末尾最大的顺子。

代码实现

package main

import (
	"bufio"
	"fmt"
	"os"
	"strings"
)

func main() {
	scanner := bufio.NewScanner(os.Stdin)

	// 读取手牌和已出牌
	var hand, played string
	if scanner.Scan() {
		hand = scanner.Text()
	}
	if scanner.Scan() {
		played = scanner.Text()
	}

	// 合并并分割所有牌
	allCards := append(
		strings.Split(hand, "-"),
		strings.Split(played, "-")...,
	)

	// 有效牌顺序
	order := []string{"3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"}
	validCards := make(map[string]bool)
	for _, card := range order {
		validCards[card] = true
	}

	// 统计每张牌的出现次数
	count := make(map[string]int)
	for _, card := range allCards {
		if validCards[card] {
			count[card]++
		}
	}

	// 计算剩余次数
	remain := make(map[string]int)
	for _, card := range order {
		remain[card] = 4 - count[card]
	}

	maxLength := 0       // 最长顺子的长度
	bestStart := 0       // 起始索引
	bestEnd := -1        // 结束索引

	// 滑动窗口遍历所有可能的起始点
	for i := 0; i < len(order); i++ {
		if remain[order[i]] <= 0 {
			continue // 起始牌不可用则跳过
		}

		currentEnd := i // 当前窗口的结束位置
		// 向右扩展窗口直到遇到不可用的牌
		for j := i; j < len(order); j++ {
			if remain[order[j]] <= 0 {
				break
			}
			currentEnd = j
		}

		currentLength := currentEnd - i + 1
		// 更新最优解(长度优先,其次比较结束位置)
		if currentLength >= 5 {
			if currentLength > maxLength || 
			   (currentLength == maxLength && currentEnd > bestEnd) {
				maxLength = currentLength
				bestStart = i
				bestEnd = currentEnd
			}
		}
	}

	// 输出结果
	if maxLength >= 5 {
		chain := strings.Join(order[bestStart:bestEnd+1], "-")
		fmt.Println(chain)
	} else {
		fmt.Println("NO-CHAIN")
	}
}

代码详细解析

  1. 输入处理

    • 使用 bufio.Scanner 读取输入的两行数据。
    • 合并手牌和已出牌,分割为数组 allCards
  2. 有效牌定义

    order := []string{"3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"}
    
    • 明确有效牌的顺序,用 map 快速判断合法性。
  3. 统计剩余次数

    remain := make(map[string]int)
    for _, card := range order {
        remain[card] = 4 - count[card]
    }
    
    • 计算每张牌的剩余次数(4 - 已用次数)。
  4. 滑动窗口遍历

    • 外层循环遍历每个可能的起始点 i
    • 内层循环从 i 向右扩展,直到遇到剩余为0的牌。
    • 记录窗口的结束位置 currentEnd
  5. 结果选择

    • 如果当前窗口长度大于历史最大值,或长度相同但结束位置更大,则更新最优解。
  6. 输出结果

    • 生成顺子字符串或输出 NO-CHAIN

示例测试

示例1输入:
3-3-4-4-5-A-5-6-2-8-3-9-10-Q-7-K-J-10-B  
A-4-5-8-8-10-C-6-7-8

输出

9-10-J-Q-K-A

解析:剩余牌中 9,10,J,Q,K,A 连续可用,形成最长顺子。

示例2输入(无顺子):
3-3-3-3  
4-4-4-4  

输出

NO-CHAIN

解析:所有牌剩余次数为0,无法组成顺子。

示例3输入(最长顺子):
3-4-5-6-7-8-9-10-J-Q-K-A  
(空已出牌)

输出

3-4-5-6-7-8-9-10-J-Q-K-A

综合分析

  1. 时间复杂度

    • 输入处理:O(n),n为输入牌数量。
    • 滑动窗口遍历:O(12²) = O(144),高效处理小规模数据。
  2. 空间复杂度

    • 统计存储:O(12),存储剩余次数和牌顺序。
  3. 优势

    • 严格顺序遍历:保证找到最长且最大的顺子。
    • 高效剪枝:遇到不可用牌立即停止扩展。
  4. 适用场景

    • 输入规模小(题目约束),适合滑动窗口法。

更多内容:

https://www.kdocs.cn/l/cvk0eoGYucWA

本文发表于【纪元A梦】,关注我,获取更多实用教程/资源!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2337723.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

足球AI模型:一款用数据分析赛事的模型

2023 年欧冠决赛前&#xff0c;某体育数据平台的 AI 模型以 78% 的概率预测曼城夺冠 —— 最终瓜迪奥拉的球队首次捧起大耳朵杯。当足球遇上 AI&#xff0c;那些看似玄学的 "足球是圆的"&#xff0c;正在被数据与算法拆解成可计算的概率命题。今天我们就来聊聊&#…

【ESP32|音频】一文读懂WAV音频文件格式【详解】

简介 最近在学习I2S音频相关内容&#xff0c;无可避免会涉及到关于音频格式的内容&#xff0c;所以刚开始接触的时候有点一头雾水&#xff0c;后面了解了下WAV相关内容&#xff0c;大致能够看懂wav音频格式是怎么样的了。本文主要为后面ESP32 I2S音频系列文章做铺垫&#xff0…

42.[前端开发-JavaScript高级]Day07-手写apply-call-bind-块级作用域

手写apply-call-bind <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdevi…

Vscode 插件开发

文章目录 1、使用vscode官方插件生成框架&#xff0c;下载脚手架2、使用脚手架初始化项目&#xff0c;这里我选择的是js3、生成的文件结构如下&#xff0c;重要的就是以下两个文件4、代码5、打包使用6、发布官网地址7、publisher ID undefined provided in the extension manif…

RTT添加一个RTC时钟驱动,以DS1307为例

添加一个外部时钟芯片 这里多了一个选项 复制drv_rtc.c,重命名为drv_rtc_ds1307.c 添加到工程中 /*** @file drv_rtc_ds1307.c* @brief * @author jiache (wanghuan3037@fiberhome.com)* @version 1.0* @date 2025-01-08* * @copyright Copyright (c) 2025 58* */ #

常见的低代码策略整理

低代码策略通过简化开发流程、降低技术门槛、提升效率&#xff0c;帮助用户快速构建灵活可靠的应用。这些策略的核心优势体现在以下方面&#xff1a; 快速交付与降本增效 减少编码需求&#xff1a;通过可视化配置&#xff08;如变量替换、表达式函数&#xff09;替代传统编码…

从彩色打印单行标准九九表学习〖代码情书〗的书写范式(Python/DeepSeek)

写给python终端的情书&#xff0c;学习代码设计/书写秘笈。 笔记模板由python脚本于2025-04-17 12:49:08创建&#xff0c;本篇笔记适合有python编程基础的coder翻阅。 【学习的细节是欢悦的历程】 博客的核心价值&#xff1a;在于输出思考与经验&#xff0c;而不仅仅是知识的简…

QML与C++:基于ListView调用外部模型进行增删改查(附自定义组件)

目录 引言相关阅读项目结构文件组织 核心技术实现1. 数据模型设计联系人项目类 (datamodel.h)数据模型类 (datamodel.h)数据模型实现 (datamodel.cpp) 2. 主程序入口点 (main.cpp)3. 主界面设计 (Main.qml)4. 联系人对话框 (ContactDialog.qml)5. 自定义组件CustomTextField.qm…

postman莫名奇妙报错,可能是注释引起的。postman 过滤请求体中的注释。

postman莫名奇妙报错&#xff0c;可能是注释引起的。postman 过滤请求体中的注释。 1、问题描述2、问题分析3、解决方法 1、问题描述 postman http请求测试时&#xff0c;如果在请求体中添加了注释&#xff0c;那么这个注释会被带到服务端执行&#xff0c;导致服务端接口返回报…

扩增子分析|基于R语言microeco包进行微生物群落网络分析(network网络、Zi-Pi关键物种和subnet子网络图)

一、引言 microeco包是福建农林大学姚敏杰教授团队开发的扩增子测序集成分析。该包综合了扩增子测序下游分析的多种功能包括群落组成、多样性、网络分析、零模型等等。通过简单的几行代码可实现复杂的分析。因此&#xff0c;microeco包发表以来被学界广泛关注&#xff0c;截止2…

中间件--ClickHouse-4--向量化执行(什么是向量?为什么向量化执行的更快?)

1、向量&#xff08;Vector&#xff09;的概念 &#xff08;1&#xff09;、向量的定义 向量&#xff1a;在计算机科学中&#xff0c;向量是一组同类型数据的有序集合&#xff0c;例如一个包含多个数值的数组。在数据库中&#xff0c;向量通常指批量数据&#xff08;如一列数…

【SpringBoot+Vue自学笔记】001

跟着这位老师学习的&#xff1a;https://www.bilibili.com/video/BV1nV4y1s7ZN?vd_sourceaf46ae3e8740f44ad87ced5536fc1a45 前后端开发技术的全栈课程&#xff1a; Java EE企业级框架&#xff1a;SpringBootMyBatisPlus Web前端核心框架&#xff1a;VueElement UI 公共云…

第十节:性能优化-如何排查组件不必要的重复渲染?

工具&#xff1a;React DevTools Profiler 方法&#xff1a;memo、shouldComponentUpdate深度对比 React 组件性能优化&#xff1a;排查与解决重复渲染问题指南 一、定位性能问题&#xff1a;React DevTools 高级用法 使用 React Developer Tools Profiler 精准定位问题组件&…

MATLAB项目实战(一)

题目&#xff1a; 某公司有6个建筑工地要开工&#xff0c;每个工地的位置&#xff08;用平面坐标系a&#xff0c;b表示&#xff0c;距离单位&#xff1a;km&#xff09;及水泥日用量d(t)由下表给出&#xff0e;目前有两个临时料场位于A(5,1)&#xff0c;B(2,7)&#xff0c;日储…

spring boot 文件下载

1.添加文件下载工具依赖 Commons IO is a library of utilities to assist with developing IO functionality. <dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>2.6</version> </depe…

微服务链路追踪:SleuthZipkin

文章目录 Sleuth & Zipkin一、Sleuth\&Zipkin介绍二、搭建环境三、Sleuth入门操作四、Zipkin搭建及操作五、RabbitMQ方式发送信息六、Elasticsearch持久化 SpringBootAdmin一、Actuator介绍二、Actuator快速入门三、SpringBootAdmin介绍四、SpringBootAdmin快速入门4.1…

java面试篇 4.9(mybatis+微服务+线程安全+线程池)

目录 mybatis&#xff1a; 1、mybatis的执行流程 2、mybatis是否支持延迟加载&#xff1f; 当我们需要去开启全局的懒加载时&#xff1a; 3、mybatis的一级和二级缓存 微服务 1、springcloud五大组件有哪些 2、服务注册和发现是什么意思&#xff1f;springcloud如何实现…

基于电子等排体的3D分子生成模型 ShEPhERD - 评测

一、背景介绍 ShEPhERD 是一个由 MIT 开发的一个 3D 相互作用感知的 ligand-based的分子生成模型&#xff0c;以 arXiv 预印本的形式发表于 2024 年&#xff0c;被ICLR2025 会议接收。文章链接&#xff1a;https://openreview.net/pdf?idKSLkFYHlYg ShEPhERD 是一种基于去噪扩…

GR00T N1:面向通用类人机器人的开放基础模型

摘要 通用型机器人需要具备多功能的身体和智能的大脑。近年来&#xff0c;类人机器人的发展在构建人类世界中的通用自主性硬件平台方面展现出巨大潜力。一个经过大量多样化数据源训练的机器人基础模型&#xff0c;对于使机器人能够推理新情况、稳健处理现实世界的多变性以及快…

QT简单实例

QT简单实例 QT简单实例一&#xff1a;通过拖动创建1.创建工程2.拖动控件实现响应3.文件目录3.1 TestQDialog.pro3.2 main.cpp3.3 dialog.h3.4 dialog.cpp 二&#xff1a;通过动态创建1.创建工程2.文件目录2.1 TestQDialogSelf.pro2.2 main.cpp2.3 dialog.h2.4 dialog.cpp QT简单…