题目解析
这道题目给出了一家餐馆的菜单,其中有 nnn 道菜,每道菜的价格和是否含有蘑菇分别由数组 aaa 和字符串 sss 描述。我们需要从这些菜中选择 kkk 道,且最多有 mmm 道含有蘑菇的菜,使得总价格尽可能低。如果无法满足条件,则输出 -1。
该问题考察了数组操作、分组处理、排序以及前缀和优化等技巧,是一道综合性较强的贪心问题。
解题思路
-
菜品分类
首先,我们可以根据菜品是否含有蘑菇,将其分成两个列表:mushroom:存储含有蘑菇的菜的价格;nonMushroom:存储不含蘑菇的菜的价格。
分类后,我们分别对这两个列表进行升序排序,以便后续能够优先选取价格最低的菜。
-
前缀和优化
在排序完成后,为了快速获取前 xxx 道菜的总价格,我们可以计算两个前缀和数组:mushPrefix[i]表示选择前 iii 道含有蘑菇的菜的总价格;nonMushPrefix[i]表示选择前 iii 道不含蘑菇的菜的总价格。
有了前缀和,任何选择的价格和都可以通过一次数组查找完成,降低了复杂度。
-
枚举蘑菇菜数量
假设我们选 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]。
-
输出结果
在所有满足条件的组合中,取价格的最小值。如果没有满足条件的方案,则返回 -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);
}
}
复杂度分析
-
时间复杂度:
- 分类和排序: O(nlogn)O(n \log n)O(nlogn)。
- 前缀和计算: O(n)O(n)O(n)。
- 枚举蘑菇菜数量: O(k)O(k)O(k)。
总体复杂度为 O(nlogn+k)O(n \log n + k)O(nlogn+k)。
-
空间复杂度:
- 使用了两个额外数组存储前缀和,空间复杂度为 O(n)O(n)O(n)。
总结
通过将问题分解为分类、排序、前缀和计算和贪心选择,我们成功地将问题复杂度降到了可接受范围,保证了解法的高效性和正确性。此方法在实际测试中表现良好,能够处理较大的输入规模。