个人题解|饭馆菜品选择问题

143 阅读5分钟

问题描述

小C来到了一家饭馆,这里共有 nn 道菜,第 ii 道菜的价格为 a_i。其中一些菜中含有蘑菇,s_i 代表第 ii 道菜是否含有蘑菇。如果 s_i = '1',那么第 ii 道菜含有蘑菇,否则没有。

小C希望点 kk 道菜,且希望总价格尽可能低。由于她不喜欢蘑菇,她希望所点的菜中最多只有 mm 道菜含有蘑菇。小C想知道在满足条件的情况下能选出的最小总价格是多少。如果无法按照要求选择菜品,则输出-1

题目分析

给定了一些菜品的价格和是否包含蘑菇的信息,要求我们在限定条件下选择 k 道菜,其中最多只能有 m 道菜包含蘑菇,并且要使得所选菜品的总价格尽可能低。若无法满足要求,则返回 -1

解决思路

  1. 分类菜品:我们可以将菜品分为两类:含有蘑菇的菜品和不含蘑菇的菜品。

  2. 排序选择:为了让总价格尽可能低,我们应当选择价格最便宜的菜品。所以我们可以分别对含蘑菇和不含蘑菇的菜品按价格进行排序,从价格最便宜的开始选择。

  3. 选择策略

    • 最多选择 m 道含蘑菇的菜,剩下的从不含蘑菇的菜中选择。
    • 如果所选菜品数不足 k,则需要从含蘑菇的菜中补充,直到总共选择了 k 道菜。
  4. 边界检查:在选择的过程中,如果没有足够的菜品可以选择,或者选出的菜品不满足限制条件(如蘑菇菜品超过 m 道),则返回 -1

详细步骤

  1. 分类并排序

    • 将所有菜品分为两组:含蘑菇和不含蘑菇,分别按照价格升序排序。
  2. 计算选择的最小价格

    • 先选择尽量多的便宜的不含蘑菇的菜品,如果不够,则选择含蘑菇的菜品,确保总共选择的菜品数为 k,且含蘑菇的菜品数不超过 m
  3. 返回结果

    • 如果能选择足够的菜品并且符合蘑菇数量限制,则返回最小总价格,否则返回 -1

代码

def solution(s: str, a: list, m: int, k: int) -> int:
        mushroom_dishes = []
        non_mushroom_dishes = []
    
        for i in range(len(s)):
            if s[i] == '1':  # 有蘑菇
                mushroom_dishes.append(a[i])
            else:  # 无蘑菇
                non_mushroom_dishes.append(a[i])
    
    # 对两类菜品按价格升序排序
        mushroom_dishes.sort()
        non_mushroom_dishes.sort()

    # 检查是否有足够的菜品
        if len(mushroom_dishes) + len(non_mushroom_dishes) < k:
            return -1

    # 尝试选择不超过 m 道含蘑菇的菜品,剩下从不含蘑菇的菜品中选择
        min_total_price = float('inf')

    # 尝试从 0 到 m 道含蘑菇的菜品
        for mushroom_count in range(max(0, k - len(non_mushroom_dishes)), min(m, len(mushroom_dishes)) + 1):
            non_mushroom_count = k - mushroom_count
            if non_mushroom_count <= len(non_mushroom_dishes):
            # 选择最便宜的mushroom_count道含蘑菇的菜品和最便宜的non_mushroom_count道非蘑菇菜品
                total_price = sum(mushroom_dishes[:mushroom_count]) + sum(non_mushroom_dishes[:non_mushroom_count])
                min_total_price = min(min_total_price, total_price)

    # 如果未找到合法的选择方案,则返回 -1
        return min_total_price if min_total_price != float('inf') else -1

if __name__ == '__main__':
    print(solution("001", [10, 20, 30], 1, 2) == 30)
    print(solution("111", [10, 20, 30], 1, 2) == -1)
    print(solution("0101", [5, 15, 10, 20], 2, 3) == 30)

复杂度分析

  1. 时间复杂度

    • 分两类菜品:O(n)。
    • 对两类菜品分别排序:O(n log n)。
    • 遍历 0 到 m 的不同蘑菇菜品数量并计算总价格:最多 O(m),但因为最大 m 为 n,实际上为 O(n)。

    因此,总时间复杂度为 O(n log n)。

  2. 空间复杂度

    • 我们需要存储两个列表(蘑菇和非蘑菇菜品),空间复杂度是 O(n)。

为什么会想到这个方法

我之所以选择这种方法,主要是基于以下几个方面的思考:

  1. 分类选择问题:题目给定了菜品是否含有蘑菇的属性,这使得我们可以把问题分成两类:含蘑菇和不含蘑菇的菜品。这样可以简化问题,因为我们只需要对两类菜品分别进行处理。

  2. 贪心策略

    • 总价格尽可能低:为了让总价格尽量低,我们应该优先选择价格便宜的菜品。这是一个典型的贪心选择策略,即每次都选择当前最便宜的菜品,直至满足选择数量为止。
    • 因此,我们对两类菜品分别排序,选择最便宜的菜品,确保价格最小化。
  3. 蘑菇限制的考虑

    • 我们的目标是选择 最多 m 道蘑菇菜品,但也必须选择 k 道菜。由于蘑菇菜品的数量是有限的,因此我们要控制在选择菜品的过程中,蘑菇菜品的数量不超过 m
    • 这样,我们就需要尝试选择不同数量的蘑菇菜品(从 0 到 m 道蘑菇菜品),然后用不含蘑菇的菜品来补足剩下的部分。
  4. 遍历不同蘑菇菜品数

    • 我们需要确保所选的菜品中蘑菇菜品数不超过 m,同时总共选 k 道菜。为了实现这一点,我们考虑遍历可能的蘑菇菜品数量,从 0 道到最多 m 道蘑菇菜品,计算每种情况下的总价格,并取最小值。

    通过这种遍历,我们能保证不会漏掉任何可能的组合,同时确保每次选择的都是最便宜的菜品。