一,题目详情
1,问题描述
在一场经典的德州扑克游戏中,有一种牌型叫做“葫芦”。“葫芦”由五张牌组成,其中包括三张相同牌面值的牌 aa 和另外两张相同牌面值的牌 bb。如果两个人同时拥有“葫芦”,我们会优先比较牌 aa 的大小,若牌 aa 相同则再比较牌 bb 的大小,牌面值的大小规则为:1 (A) > K > Q > J > 10 > 9 > ... > 2,其中 1 (A) 的牌面值为1,K 为13,依此类推。
在这个问题中,我们对“葫芦”增加了一个限制:组成“葫芦”的五张牌牌面值之和不能超过给定的最大值 maxmax。
给定一组牌,你需要找到符合规则的最大的“葫芦”组合,并输出其中三张相同的牌面和两张相同的牌面。如果找不到符合条件的“葫芦”,则输出 “0, 0”。
2,测试样例
样例1:
输入:
n = 9, max = 34, array = [6, 6, 6, 8, 8, 8, 5, 5, 1]
输出:[8, 5]
说明:array数组中可组成4个葫芦,分别为[6,6,6,8,8],[6,6,6,5,5],[8,8,8,6,6],[8,8,8,5,5]。其中[8,8,8,6,6]的牌面值为36,大于34不符合要求。剩下的3个葫芦的大小关系为[8,8,8,5,5]>[6,6,6,8,8]>[6,6,6,5,5],故返回[8,5]
样例2:
输入:
n = 9, max = 37, array = [9, 9, 9, 9, 6, 6, 6, 6, 13]
输出:[6, 9]
说明:可组成2个葫芦,分别为[9,9,9,6,6]和[6,6,6,9,9],由于[9,9,9,6,6]的牌面值为39,大于37,故返回[6,9]
样例3:
输入:
n = 9, max = 40, array = [1, 11, 13, 12, 7, 8, 11, 5, 6]
输出:[0, 0]
说明:无法组成任何葫芦,故返回[0,0]
样例4:
输入:
n = 6, max = 50, array = [13, 13, 13, 1, 1, 1]
输出:[1, 13]
说明:可组成两个葫芦,分别为[A,A,A,K,K]和[K,K,K,A,A],两者牌面值都小于50,故都合法。因为三张相同牌面值的A > K,故[A,A,A,K,K]比[K,K,K,A,A]要大,返回[1,13]
二、解题思路
1. 问题分析
-
输入特性:
- 输入数组
array包含若干个牌面值,可能包含重复值。 - 牌面值的大小规则为:1 (A) > K > Q > J > 10 > 9 > ... > 2。
- 组合的五张牌牌面值之和不能超过给定的最大值
max。
- 输入数组
-
输出要求:
- 找到符合条件的最大“葫芦”组合,输出三张相同的牌面值和两张相同的牌面值。
- 如果找不到符合条件的“葫芦”,则输出
[0, 0]。
2. 详细分析
-
统计牌面值频率:
- 使用
collections.defaultdict统计每个牌面值在数组中出现的次数。
- 使用
-
生成候选列表:
- 找出所有出现次数大于等于3的牌面值作为三张牌的候选列表
a_candidates。 - 找出所有出现次数大于等于2的牌面值作为对子的候选列表
b_candidates。 - 按照牌面优先级降序排列,其中 A 的优先级最高。
- 找出所有出现次数大于等于3的牌面值作为三张牌的候选列表
-
遍历候选组合:
- 遍历所有可能的
(a, b)组合,计算其牌面值之和。 - 如果满足条件(牌面值之和 ≤
max),则判断当前组合是否比已记录的最优组合更大。
- 遍历所有可能的
-
返回结果:
- 如果找到符合条件的“葫芦”,返回
[max_a, max_b]。 - 如果没有找到,返回
[0, 0]。
- 如果找到符合条件的“葫芦”,返回
三、代码实现
def solution(n, max_sum, array):
from collections import defaultdict
def is_larger(x, y):
"""判断牌面x是否比y大"""
if x == 1:
return y != 1 # x是A,只有当y不是A时,x更大
elif y == 1:
return False # y是A,x不是,则y更大
else:
return x > y # 否则比较数值大小,因为非A的牌面数值越大,牌面越大
freq = defaultdict(int)
for num in array:
freq[num] += 1
# 生成候选列表,并按牌面优先级降序排列
a_candidates = sorted(
[num for num, count in freq.items() if count >= 3],
key=lambda x: (0 if x == 1 else 14 - x) # 1排在最前,其他按数值降序
)
b_candidates = sorted(
[num for num, count in freq.items() if count >= 2],
key=lambda x: (0 if x == 1 else 14 - x)
)
max_a, max_b = 0, 0
for a in a_candidates:
for b in b_candidates:
if a == b:
continue # 跳过a和b相同的情况
total = 3 * a + 2 * b
if total <= max_sum:
# 判断当前组合是否比已记录的最优组合更大
if (max_a == 0 and max_b == 0) or is_larger(a, max_a) or (a == max_a and is_larger(b, max_b)):
max_a, max_b = a, b
return [max_a, max_b] if max_a != 0 or max_b != 0 else [0, 0]
if __name__ == "__main__":
print(solution(9, 34, [6, 6, 6, 8, 8, 8, 5, 5, 1]) == [8, 5])
print(solution(9, 37, [9, 9, 9, 9, 6, 6, 6, 6, 13]) == [6, 9])
print(solution(9, 40, [1, 11, 13, 12, 7, 8, 11, 5, 6]) == [0, 0])
print(solution(6, 50, [13, 13, 13, 1, 1, 1]) == [1, 13])
print(solution(31, 42, [3,3,11,12,12,2,13,5,13,1,13,8,8,1,8,13,12,9,2,11,3,5,8,11,1,11,1,5,4,2,5]) == [1, 13])
四、逐步推演(以样例1为例)
输入:
n = 9
max = 34
array = [6, 6, 6, 8, 8, 8, 5, 5, 1]
推演过程如下:
| 步骤 | 操作 | 结果 |
|---|---|---|
| 1 | 统计频率 | freq = {6: 3, 8: 3, 5: 2, 1: 1} |
| 2 | 生成候选列表 | a_candidates = [8, 6],b_candidates = [8, 6, 5] |
| 3 | 遍历组合 (a, b) | |
| 3.1 | (a=8, b=5) | 牌面值之和 = 3×8 + 2×5 = 34,符合条件,记录 [8, 5] |
| 3.2 | (a=6, b=5) | 牌面值之和 = 3×6 + 2×5 = 28,符合条件,但 [6, 5] < [8, 5] |
| 3.3 | (a=6, b=8) | 牌面值之和 = 3×6 + 2×8 = 34,符合条件,但 [6, 8] < [8, 5] |
| 4 | 返回结果 | [8, 5] |
最终结果为 [8, 5],与预期一致。
五、复杂度分析
-
时间复杂度:O(n)
- 统计频率、生成候选列表、遍历组合均为线性操作。
-
空间复杂度:O(1)
- 仅使用常数级额外空间。
六、总结
通过统计牌面值频率、生成候选列表、遍历组合并比较大小,我们实现了一个高效且简洁的解决方案。该算法具有以下特点:
- 高效性:时间复杂度为 O(n),空间复杂度为 O(1)。
- 鲁棒性:能够处理多种输入情况,包括无法组成“葫芦”的情况。
- 易读性:代码逻辑清晰,易于理解和维护。