使用 Python 解题 - 寻找最大葫芦

160 阅读5分钟

一,题目详情

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]

image.png

二、解题思路

1. 问题分析

  1. 输入特性

    • 输入数组 array 包含若干个牌面值,可能包含重复值。
    • 牌面值的大小规则为:1 (A) > K > Q > J > 10 > 9 > ... > 2。
    • 组合的五张牌牌面值之和不能超过给定的最大值 max
  2. 输出要求

    • 找到符合条件的最大“葫芦”组合,输出三张相同的牌面值和两张相同的牌面值。
    • 如果找不到符合条件的“葫芦”,则输出 [0, 0]

2. 详细分析

  1. 统计牌面值频率

    • 使用 collections.defaultdict 统计每个牌面值在数组中出现的次数。
  2. 生成候选列表

    • 找出所有出现次数大于等于3的牌面值作为三张牌的候选列表 a_candidates
    • 找出所有出现次数大于等于2的牌面值作为对子的候选列表 b_candidates
    • 按照牌面优先级降序排列,其中 A 的优先级最高。
  3. 遍历候选组合

    • 遍历所有可能的 (a, b) 组合,计算其牌面值之和。
    • 如果满足条件(牌面值之和 ≤ max),则判断当前组合是否比已记录的最优组合更大。
  4. 返回结果

    • 如果找到符合条件的“葫芦”,返回 [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],与预期一致。


五、复杂度分析

  1. 时间复杂度:O(n)

    • 统计频率、生成候选列表、遍历组合均为线性操作。
  2. 空间复杂度:O(1)

    • 仅使用常数级额外空间。

六、总结

通过统计牌面值频率、生成候选列表、遍历组合并比较大小,我们实现了一个高效且简洁的解决方案。该算法具有以下特点:

  1. 高效性:时间复杂度为 O(n),空间复杂度为 O(1)。
  2. 鲁棒性:能够处理多种输入情况,包括无法组成“葫芦”的情况。
  3. 易读性:代码逻辑清晰,易于理解和维护。

9b144a7dec517e1c73defc28da2d0ca.png