2025 B卷 100分 题型
本文涵盖详细的问题分析、解题思路、代码实现、代码详解、测试用例以及综合分析;
并提供Java、python、JavaScript、C++、C语言、GO六种语言的最佳实现方式!
本文收录于专栏:《2025华为OD真题目录+全流程解析/备考攻略/经验分享》
华为OD机试真题《最长的顺子》:
题目名称:最长的顺子
知识点:字符串、动态规划/滑动窗口、逻辑处理
时间限制:1秒
空间限制:256MB
语言限制:不限
题目描述
输入当前手牌和已出牌,计算对手可能组成的最长顺子(连续5-12张牌,不含2和大小王)。若存在多个最长顺子,输出牌面最大的那个,否则返回"NO-CHAIN"。
规则说明
- 顺子定义:连续递增的牌序列,范围从3到A(3<4<5<6<7<8<9<10<J<Q<K<A),不含2、B(小王)、C(大王)。
- 输入格式:
- 第一行为当前手牌(如
3-3-4-5-A
) - 第二行为已出牌(如
A-4-5-6-7
)
- 第一行为当前手牌(如
- 输出格式:最长顺子,若长度相同则输出牌面最大的(如
9-10-J-Q-K-A
优于5-6-7-8-9
)。 - 数据约束:每张牌初始有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"。
解题思路
- 统计剩余牌数:根据手牌和已出牌,计算每个有效牌的剩余数量。
- 滑动窗口遍历:从每个可能的起始牌开始,尽可能向右扩展窗口,直到遇到剩余不足的牌。
- 选择最优结果:记录最长窗口,若长度相同则选择末尾最大的窗口。
代码实现
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");
}
}
}
代码详细解析
- 输入处理:读取手牌和已出牌,分割为列表。
- 有效牌集合:定义合法的顺子牌和顺序。
- 统计次数:遍历所有牌,统计每个有效牌的出现次数。
- 剩余计算:每种牌最多4张,剩余次数为
4 - 已用次数
。 - 滑动窗口:
- 外层遍历每个起始点
i
,若该牌剩余可用则继续。 - 内层扩展窗口
j
,直到遇到剩余为0的牌,记录窗口结束位置。 - 更新最长且最大的顺子。
- 外层遍历每个起始点
- 结果输出:根据窗口生成顺子字符串或输出“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
综合分析
- 时间复杂度:O(n²),n为有效牌数量(12),双重循环遍历。
- 空间复杂度:O(n),存储剩余次数和牌顺序。
- 优势:滑动窗口确保找到最优解,处理高效。
- 适用场景:适用于题目约束的小规模输入,快速找到最长顺子。
python
问题分析
我们需要找到对手可能组成的最长顺子。顺子由3到A的连续牌组成,不含2和大小王。输出最长顺子,若存在多个相同长度的,选择牌面最大的那个。若无法组成,返回"NO-CHAIN"。
解题思路
- 输入处理:将手牌和已出牌合并,统计每张牌的出现次数。
- 剩余牌计算:每张牌最多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