5. 寻找最大葫芦 | 豆包MarsCode AI 刷题

379 阅读7分钟

问题背景

在德州扑克中,“葫芦”是一种由五张牌组成的牌型,具体要求是其中有三张相同牌面值的牌(记为a)和另外两张相同牌面值的牌(记为b)。在比较两个“葫芦”时,首先比较三张相同牌a的大小,若a相同,则比较两张相同牌b的大小。

本题在经典“葫芦”牌型的基础上增加了一个限制:组成“葫芦”的五张牌的牌面值之和不能超过给定的最大值max。此外,牌面值的大小顺序为:A > K > Q > J > 10 > 9 > ... > 2,其中A的牌面值为1,K为13,依此类推。

给定一组牌,要求找出符合上述规则的最大“葫芦”组合,并输出其中三张相同牌a和两张相同牌b的牌面值。如果无法找到符合条件的“葫芦”,则输出[0, 0]

问题分析

要解决这个问题,我们需要完成以下几个步骤:

  1. 统计牌面值频率:首先,我们需要统计每种牌面值出现的次数,以便确定哪些牌可以作为a(至少出现3次)和b(至少出现2次)。
  2. 排序牌面值:根据题目中给定的牌面值大小规则,对牌面值进行排序,以便优先选择更大的牌面值作为a和b。
  3. 枚举可能的组合:遍历所有可能的a和b组合,确保它们满足出现次数和总和不超过max的条件。
  4. 选择最佳组合:在满足条件的组合中,选择a最大的组合,如果a相同,则选择b最大的组合。
  5. 处理无解情况:如果没有任何组合满足条件,则返回[0, 0]

1. 统计牌面值频率

为了高效地统计每种牌面值出现的次数,我们可以使用一个频率数组freq,其中freq[i]表示牌面值为i的牌出现的次数。由于牌面值范围为1到13,我们可以将数组大小设为14(索引0未使用)。

2. 排序牌面值

根据题目中的规则,牌面值的大小顺序为A > K > Q > J > 10 > ... > 2,其中A的牌面值为1,K为13,依此类推。为了简化比较,我们可以将A(1)的排名设为14,其余牌面的排名即为其数值本身。这样,A在排序时将位于最前面。

排序后,牌面值按从高到低排列,确保在后续步骤中优先选择更大的a和b。

3. 枚举可能的组合

在排序后的牌面值列表中,我们首先选择一个a,要求其出现次数至少为3。然后,在剩余的牌面值中选择一个b,要求其出现次数至少为2。计算a和b的总和,确保不超过max

4. 选择最佳组合

由于我们已按牌面值从高到低排序,第一次找到满足条件的a和b组合即为最佳组合。这是因为更高的a优先于更高的b,因此不需要继续枚举其他组合。

5. 处理无解情况

如果在所有可能的a和b组合中,没有任何组合满足出现次数和总和不超过max的条件,则返回[0, 0],表示无法构成符合条件的“葫芦”。

算法实现

基于上述分析,我们可以使用C++编写如下算法:

  1. 输入处理:读取牌的数量n,最大总和max_sum,以及牌面值数组array
  2. 频率统计:遍历数组,统计每种牌面值的出现次数。
  3. 牌面值排序:生成一个唯一的牌面值列表,并根据规则进行排序。
  4. 枚举组合:遍历排序后的牌面值,寻找符合条件的a和b组合。
  5. 返回结果:如果找到符合条件的组合,返回对应的a和b;否则,返回[0, 0]

下面是基于上述步骤的C++函数实现:

#include <vector>
#include <algorithm>

std::vector<int> solution(int n, int max_sum, const std::vector<int>& array) {
    std::vector<int> freq(14, 0); // 索引1到13表示牌面值1到13
    for(int x : array) {
        if(x >= 1 && x <= 13) {
            freq[x]++;
        }
    }

    // 创建一个唯一的牌面值列表
    std::vector<int> unique_cards;
    for(int val = 1; val <= 13; val++) {
        if(freq[val] > 0) {
            unique_cards.push_back(val);
        }
    }

    // 按照规则排序,A(1)排在最前,其余按数值降序
    auto compare_rank = [](int a, int b) {
        int rank_a = (a == 1) ? 14 : a;
        int rank_b = (b == 1) ? 14 : b;
        return rank_a > rank_b;
    };
    std::sort(unique_cards.begin(), unique_cards.end(), compare_rank);

    // 枚举可能的a和b组合
    for(int a : unique_cards) {
        if(freq[a] >= 3) { // a至少出现3次
            for(int b : unique_cards) {
                if(b != a && freq[b] >= 2) { // b至少出现2次,且不同于a
                    long long current_sum = 3LL * a + 2LL * b;
                    if(current_sum <= max_sum) {
                        return {a, b}; // 找到最优组合,直接返回
                    }
                }
            }
        }
    }

    // 如果没有找到符合条件的组合,返回[0, 0]
    return {0, 0};
}

复杂度分析

时间复杂度

  • 频率统计:遍历数组array一次,时间复杂度为O(n)。
  • 创建和排序唯一牌面值列表:最多有13种牌面值,排序时间复杂度为O(13 log 13) ≈ O(1)。
  • 枚举组合:嵌套遍历,外层最多13次,内层最多13次,总时间复杂度为O(13^2) ≈ O(1)。

综上,整体时间复杂度为O(n),主要受输入规模n的影响。

空间复杂度

  • 频率数组:固定大小的数组,O(1)。
  • 唯一牌面值列表:最多13个元素,O(1)。
  • 返回结果:固定大小的向量,O(1)。

因此,空间复杂度为O(1)。

边界条件与测试用例

在编写和测试算法时,需要考虑各种边界条件,以确保算法的健壮性。以下是一些重要的边界条件及相应的测试用例:

  1. 最小输入

    • n=0,max_sum=0,array为空。
    • 预期输出:[0, 0]。
  2. 没有足够的牌形成“葫芦”

    • 牌中没有任何三张相同的牌或没有两张相同的牌。
    • 预期输出:[0, 0]。
  3. 恰好满足条件的“葫芦”

    • 有且仅有一组a和b组合,且总和等于max_sum
    • 预期输出:[a, b]。
  4. 多组可能的“葫芦”

    • 多组a和b组合满足条件,需选择a最大的组合,若a相同,则选择b最大的组合。
    • 预期输出:[a, b]其中a和b为最优组合。
  5. 包含A牌

    • A(1)作为a或b,需确保其在排序中优先级最高。
    • 预期输出:[1, b]或[a, 1],根据出现次数和总和条件。

示例测试用例

  • 样例1

    • 输入:n = 9, max_sum = 34, array = [6, 6, 6, 8, 8, 8, 5, 5, 1]
    • 输出:[8, 5]
    • 解析:两个可能的“葫芦”组合为[6, 8]和[8, 5]。[8, 5]的总和为38 + 25 = 34,恰好等于max_sum,且a更大。
  • 样例2

    • 输入:n = 9, max_sum = 37, array = [9, 9, 9, 9, 6, 6, 6, 6, 13]
    • 输出:[6, 9]
    • 解析:可能的组合为[9, 6]和[6, 9]。尽管a相同,两种组合的总和分别为39 + 26 = 33和36 + 29 = 30。根据题目要求优先选择a更大的组合,因此选择[9, 6]。但由于题目要求在a相同情况下选择b更大的组合,此处需注意具体实现逻辑。
  • 样例3

    • 输入:n = 9, max_sum = 40, array = [1, 11, 13, 12, 7, 8, 11, 5, 6]
    • 输出:[0, 0]
    • 解析:无法形成“葫芦”,因为没有任何牌面值出现至少3次。

代码优化与改进

虽然当前的实现已经能够高效解决问题,但在实际应用中,可以考虑以下几点优化:

  1. 提前终止:一旦找到符合条件的最佳组合,即可立即返回,避免不必要的遍历。
  2. 减少不必要的比较:在选择b时,可以跳过a的选择范围,从而减少嵌套循环的次数。
  3. 使用更高效的数据结构:在本题中,由于牌面值范围有限,使用数组进行频率统计已经是最优选择。
  4. 处理A牌的特殊情况:确保在排序和比较时,A(1)牌的优先级最高,避免因索引混淆导致错误。