我一开始看这道题的难度系数以为挺简单的,但是在做的过程中发现还挺复杂的,例如要考虑 A 这个特殊牌以及b的牌面值有可能比a的牌面值大,我在做的时候做着做着就忽略了牌 A 这个特殊点,在测试的时候才发现。现在我们进入正题,在 “寻找最大葫芦” 这一问题情境中,我们置身于德州扑克的牌型世界,目标是在给定的一组牌和牌面值总和限制条件下,精准找出符合要求的最大 “葫芦” 组合。
首先,让我们一起来剖析解题思路。我们需要对输入的牌数组进行统计分析,确定每张牌的数量。所以我借助了一个HashMap来存储牌面值及其对应的出现次数。考虑到牌面值的特殊表示(如 A 可能用 1 或 14 表示等),所以我在一开始创建了一个cardValueMap来进行牌面值的转换映射,方便统一处理。在统计完牌的数量后,我们的核心任务是寻找满足 “葫芦” 条件(三张相同牌面值和两张相同牌面值)且牌面值总和不超过给定最大值max的组合。采用优先队列(PriorityQueue)来辅助寻找过程,一个最大堆maxHeap用于存储所有不同的牌面值,按照从大到小的顺序排列,这样便于先从较大牌面值开始尝试构建 “葫芦”。当从maxHeap中取出一个可能作为三张相同牌面值的牌a后,为了寻找与之匹配的两张相同牌面值的牌b,创建一个备份堆backupHeap,它与maxHeap具有相同元素但独立操作,在backupHeap中遍历寻找满足条件的b。若找到a和b且它们的牌面值总和不超过max,则找到了符合要求的 “葫芦”,直接返回结果;若遍历完所有可能都未找到,则返回[0, 0]表示不存在符合条件的 “葫芦”。
我最开始没想到用这个备份堆,所以在运行的时候发现有的情况运行错误(结果是b的牌面值比a的牌面值大的情况),通过打断点一步一步运行才发现不用备份堆找b永远是在a选定的牌面值后面的值,所以才想到用备份堆。
下面深入解析代码:
import java.util.HashMap;
import java.util.Map;
import java.util.PriorityQueue;
public class Main {
// 预先定义牌面值映射表,处理特殊牌面值表示
private static final Map<Integer, Integer> cardValueMap = new HashMap<>();
static {
cardValueMap.put(1, 14);
cardValueMap.put(13, 13);
cardValueMap.put(12, 12);
cardValueMap.put(11, 11);
cardValueMap.put(10, 10);
cardValueMap.put(9, 9);
cardValueMap.put(8, 8);
cardValueMap.put(7, 7);
cardValueMap.put(6, 6);
cardValueMap.put(5, 5);
cardValueMap.put(4, 4);
cardValueMap.put(3, 3);
cardValueMap.put(2, 2);
}
public static int[] solution(int n, int max, int[] array) {
// 用于统计牌面值及其出现次数
Map<Integer, Integer> map = new HashMap<>();
// 遍历输入牌数组,统计牌数量并转换牌面值
for (int i = 0; i < n; i++) {
int mappedValue = cardValueMap.getOrDefault(array[i], array[i]);
map.put(mappedValue, map.getOrDefault(mappedValue, 0) + 1);
}
// 创建最大堆,存储所有不同牌面值,按从大到小排序
PriorityQueue<Integer> maxHeap = new PriorityQueue<>((a, b) -> b - a);
maxHeap.addAll(map.keySet());
// 创建备份堆,初始与最大堆相同
PriorityQueue<Integer> backupHeap = new PriorityQueue<>(maxHeap);
// 遍历最大堆,寻找三张相同牌面值的牌 a
while (!maxHeap.isEmpty()) {
int a = maxHeap.poll();
// 如果牌 a 的数量足够组成三张相同牌
if (map.get(a) >= 3) {
// 重置备份堆,用于寻找两张相同牌面值的牌 b
backupHeap = new PriorityQueue<>((c, d) -> d - c);
backupHeap.addAll(map.keySet());
// 在备份堆中寻找与牌 a 不同且数量足够组成两张相同牌的牌 b
while (!backupHeap.isEmpty()) {
int b = backupHeap.poll();
if (b!= a && map.get(b) >= 2) {
// 处理特殊牌面值 A(14)转换为 1
if (a == 14) {
a = 1;
}
if (b == 14) {
b = 1;
}
// 计算牌 a 和牌 b 组成的“葫芦”牌面值总和
int sum = a * 3 + b * 2;
// 如果总和不超过给定最大值,返回结果
if (sum <= max) {
return new int[]{a, b};
}
}
}
}
}
// 如果未找到符合条件的“葫芦”,返回 [0, 0]
return new int[]{0, 0};
}
public static void main(String[] args) {
System.out.println(java.util.Arrays.equals(solution(9, 34, new int[]{6, 6, 6, 8, 8, 8, 5, 5, 1}), new int[]{8, 5}));
System.out.println(java.util.Arrays.equals(solution(9, 37, new int[]{9, 9, 9, 9, 6, 6, 6, 6, 13}), new int[]{6, 9}));
System.out.println(java.util.Arrays.equals(solution(9, 40, new int[]{1, 11, 13, 12, 7, 8, 11, 5, 6}), new int[]{0, 0}));
}
}
在solution方法中,首先构建牌面值统计map,通过循环遍历输入数组array,借助cardValueMap转换牌面值并统计数量。接着创建maxHeap和backupHeap,然后进入外层循环遍历maxHeap。当找到合适的a后,重置backupHeap并在内层循环中寻找b。一旦找到满足条件的a和b组合,经牌面值转换和总和计算验证后,立即返回结果数组。若整个循环结束都未找到,则返回[0, 0]。
例如,对于输入n = 9, max = 34, array = [6, 6, 6, 8, 8, 8, 5, 5, 1],首先统计牌面值数量,map中会记录6出现 3 次,8出现 3 次,5出现 2 次等。maxHeap会按从大到小顺序存储14(1转化成了14)、8、6等牌面值。外层循环先取出a = 14,由于其数量不够,进入下一次循环取下一个a = 8,由于其数量足够,进入内层循环,在backupHeap中按规则找到b = 5,计算总和8 * 3 + 5 * 2 = 34,未超过max,所以返回[8, 5]。
最后考虑时间复杂度和空间复杂度。从时间复杂度分析,主要操作包括统计牌面值数量和两次堆的遍历操作。统计牌面值数量的时间复杂度为O(n),其中n为牌的数量。堆的操作时间复杂度取决于堆中元素数量,在最坏情况下,每次遍历堆都需要处理所有不同牌面值,假设牌面值种类数为k,则堆操作的时间复杂度约为O(k*k),总体时间复杂度约为O(n+k*k)。在空间复杂度方面,主要消耗在map和两个PriorityQueue上,其空间占用取决于牌面值种类数和牌的数量,空间复杂度约为O(n+k)。这种算法通过合理的数据结构运用和有序的遍历策略,有效地解决了 “寻找最大葫芦” 问题。