豆包MarsCode AI刷题心得3——36.饭馆菜品选择问题 | 豆包MarsCode AI刷题

163 阅读3分钟

题目解析

这道题目给出了一家餐馆的菜单,其中有 nnn 道菜,每道菜的价格和是否含有蘑菇分别由数组 aaa 和字符串 sss 描述。我们需要从这些菜中选择 kkk 道,且最多有 mmm 道含有蘑菇的菜,使得总价格尽可能低。如果无法满足条件,则输出 -1。

该问题考察了数组操作、分组处理、排序以及前缀和优化等技巧,是一道综合性较强的贪心问题。


解题思路

  1. 菜品分类
    首先,我们可以根据菜品是否含有蘑菇,将其分成两个列表:

    • mushroom:存储含有蘑菇的菜的价格;
    • nonMushroom:存储不含蘑菇的菜的价格。
      分类后,我们分别对这两个列表进行升序排序,以便后续能够优先选取价格最低的菜。
  2. 前缀和优化
    在排序完成后,为了快速获取前 xxx 道菜的总价格,我们可以计算两个前缀和数组:

    • mushPrefix[i] 表示选择前 iii 道含有蘑菇的菜的总价格;
    • nonMushPrefix[i] 表示选择前 iii 道不含蘑菇的菜的总价格。
      有了前缀和,任何选择的价格和都可以通过一次数组查找完成,降低了复杂度。
  3. 枚举蘑菇菜数量
    假设我们选 xxx 道含蘑菇的菜,则需要从不含蘑菇的菜中选 k−xk - xk−x 道。我们可以枚举 xxx 的取值范围为 [0,min⁡(m,k)][0, \min(m, k)][0,min(m,k)],并在每种情况下检查是否有足够的菜满足选择要求。

    • 如果 x>mushroom.size()x > \text{mushroom.size()}x>mushroom.size() 或 k−x>nonMushroom.size()k - x > \text{nonMushroom.size()}k−x>nonMushroom.size(),则该组合无效。
    • 如果有效,则总价格为 mushPrefix[x] + nonMushPrefix[k - x]
  4. 输出结果
    在所有满足条件的组合中,取价格的最小值。如果没有满足条件的方案,则返回 -1。


代码实现

以下是完整的 Java 实现代码:

java
Copy code
import java.util.*;

public class Main {
    public static long solution(String s, int[] a, int m, int k) {
        int n = s.length();
        List<Integer> mushroom = new ArrayList<>();
        List<Integer> nonMushroom = new ArrayList<>();

        // 分类菜品
        for (int i = 0; i < n; i++) {
            if (s.charAt(i) == '1') {
                mushroom.add(a[i]);
            } else {
                nonMushroom.add(a[i]);
            }
        }

        // 按价格升序排序
        Collections.sort(mushroom);
        Collections.sort(nonMushroom);

        // 前缀和用于快速计算价格
        long[] mushPrefix = new long[mushroom.size() + 1];
        long[] nonMushPrefix = new long[nonMushroom.size() + 1];

        for (int i = 1; i <= mushroom.size(); i++) {
            mushPrefix[i] = mushPrefix[i - 1] + mushroom.get(i - 1);
        }
        for (int i = 1; i <= nonMushroom.size(); i++) {
            nonMushPrefix[i] = nonMushPrefix[i - 1] + nonMushroom.get(i - 1);
        }

        long minPrice = Long.MAX_VALUE;

        // 遍历所有可能的蘑菇菜数量
        for (int x = 0; x <= Math.min(m, k); x++) {
            int y = k - x; // 非蘑菇菜的数量
            if (x <= mushroom.size() && y <= nonMushroom.size()) {
                long price = mushPrefix[x] + nonMushPrefix[y];
                minPrice = Math.min(minPrice, price);
            }
        }

        // 如果没有找到满足条件的组合,返回-1
        return minPrice == Long.MAX_VALUE ? -1 : minPrice;
    }

    public static void main(String[] args) {
        System.out.println(solution("001", new int[]{10, 20, 30}, 1, 2) == 30);
        System.out.println(solution("111", new int[]{10, 20, 30}, 1, 2) == -1);
        System.out.println(solution("0101", new int[]{5, 15, 10, 20}, 2, 3) == 30);
    }
}

复杂度分析

  1. 时间复杂度:

    • 分类和排序: O(nlog⁡n)O(n \log n)O(nlogn)。
    • 前缀和计算: O(n)O(n)O(n)。
    • 枚举蘑菇菜数量: O(k)O(k)O(k)。
      总体复杂度为 O(nlog⁡n+k)O(n \log n + k)O(nlogn+k)。
  2. 空间复杂度:

    • 使用了两个额外数组存储前缀和,空间复杂度为 O(n)O(n)O(n)。

总结

通过将问题分解为分类、排序、前缀和计算和贪心选择,我们成功地将问题复杂度降到了可接受范围,保证了解法的高效性和正确性。此方法在实际测试中表现良好,能够处理较大的输入规模。