今天和大家分享一道题库中一道经典的贪心排序题:饭馆菜品选择问题,加油冲!O(∩_∩)O~~
饭馆菜品选择问题原题地址:www.marscode.cn/practice/dn…
问题描述
小C来到了一家饭馆,这里共有 n 道菜,第 i 道菜的价格为 a_i。其中一些菜中含有蘑菇,s_i 代表第 i 道菜是否含有蘑菇。如果 s_i = '1',那么第 i 道菜含有蘑菇,否则没有。
小C希望点 k 道菜,且希望总价格尽可能低。由于她不喜欢蘑菇,她希望所点的菜中最多只有 m 道菜含有蘑菇。小C想知道在满足条件的情况下能选出的最小总价格是多少。如果无法按照要求选择菜品,则输出-1。
测试样例
样例1:
输入:
s = "001", a = [10, 20, 30], m = 1, k = 2
输出:30
样例2:
输入:
s = "111", a = [10, 20, 30], m = 1, k = 2
输出:-1
样例3:
输入:
s = "0101", a = [5, 15, 10, 20], m = 2, k = 3
输出:30
题目分析
这个问题是一个典型的组合优化问题,我们需要在满足特定约束条件下找到最小总价格的菜品组合。具体来说,我们需要从给定的菜品中选择 ( k ) 道菜,使得总价格尽可能低,同时确保含有蘑菇的菜品数量不超过 ( m )。
-
输入参数:
- ( s ):一个字符串,表示每道菜是否含有蘑菇('1' 表示含有蘑菇,'0' 表示不含有)。
- ( a ):一个整数数组,表示每道菜的价格。
- ( m ):一个整数,表示最多可以选择的含有蘑菇的菜品数量。
- ( k ):一个整数,表示需要选择的菜品数量。
-
输出:
- 一个整数,表示在满足条件的情况下能选出的最小总价格。如果无法按照要求选择菜品,则输出 -1。
算法设计
-
数据结构:
- 使用一个向量
dishes来存储每道菜的价格和是否含有蘑菇的信息。
- 使用一个向量
-
排序:
- 将
dishes按价格从小到大排序,这样我们可以优先选择价格较低的菜品。
- 将
-
选择菜品:
- 遍历排序后的
dishes,选择菜品直到满足 ( k ) 道菜的要求。 - 在选择过程中,记录含有蘑菇的菜品数量,确保不超过 ( m )。
- 遍历排序后的
-
检查结果:
- 如果在遍历结束后选择了 ( k ) 道菜,计算总价格并返回。
- 如果没有选择足够的菜品(即少于 ( k ) 道),返回 -1。
代码实现
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <numeric>
using namespace std;
long solution(const string& s, const vector<int>& a, int m, int k) {
// 创建一个包含菜品价格和是否含蘑菇信息的向量
vector<pair<int, bool>> dishes;
for (size_t i = 0; i < s.size(); ++i) {
dishes.emplace_back(a[i], s[i] == '1');
}
// 按价格对菜品进行排序
sort(dishes.begin(), dishes.end());
long minCost = 0;
int mushroomCount = 0;
int dishCount = 0;
// 选择菜品
for (const auto& dish : dishes) {
if (dishCount == k) break; // 如果已经选择了k道菜,则停止
if (dish.second) {
if (mushroomCount == m) continue; // 如果含蘑菇的菜品数量已达上限,则跳过
++mushroomCount;
}
minCost += dish.first;
++dishCount;
}
// 检查是否成功选择了k道菜
if (dishCount < k) return -1; // 如果没有选择足够的菜品,则返回-1
return minCost;
}
int main() {
// 测试样例1
string s1 = "001";
vector<int> a1 = {10, 20, 30};
int m1 = 1, k1 = 2;
cout << solution(s1, a1, m1, k1) << endl; // 输出:30
// 测试样例2
string s2 = "111";
vector<int> a2 = {10, 20, 30};
int m2 = 1, k2 = 2;
cout << solution(s2, a2, m2, k2) << endl; // 输出:-1
// 测试样例3
string s3 = "0101";
vector<int> a3 = {5, 15, 10, 20};
int m3 = 2, k3 = 3;
cout << solution(s3, a3, m3, k3) << endl; // 输出:30
return 0;
}
总结详细说明
贪心算法是一种在每一步选择中都采取当前状态下最好或最优的选择,从而希望导致结果是全局最好或最优的算法。在解决“饭馆菜品选择问题”时,我们运用了贪心算法的思想,具体总结如下:
- 局部最优选择:在每一步选择中,我们都选择了当前未选择菜品中价格最低的菜品。这是因为我们希望总价格尽可能低,而选择价格最低的菜品显然是朝着这个目标迈进的局部最优解。
- 排序的重要性:为了能够实现局部最优选择,我们首先对所有菜品按价格进行了排序。排序是贪心算法中常用的预处理步骤,它帮助我们简化了后续的选择过程。
- 约束条件的处理:在选择菜品的过程中,我们必须考虑到两个约束条件:所选菜品数量(k)和含蘑菇菜品数量(m)。我们在选择过程中实时检查这些约束条件,确保不会超出限制。
- 边界情况的处理:在算法实现中,我们需要特别注意边界情况,例如,当所有菜品都含蘑菇时,如果 m 小于 k,则无法选出满足条件的菜品组合,此时应返回 -1。
贪心算法的注意事项
- 适用性:贪心算法并不总是能得到最优解。它适用于问题具有“最优子结构”和“贪心选择性质”的情况。在“饭馆菜品选择问题”中,选择价格最低的菜品直到满足数量要求,可以保证得到最优解。
- 局部最优与全局最优:在应用贪心算法时,必须确保局部最优解能导致全局最优解。这通常需要数学证明或通过反例来验证。
- 约束条件的考虑:贪心算法在每一步选择时,除了考虑当前最优选择外,还必须考虑所有相关的约束条件,确保每一步的选择都是可行的。
- 证明最优性:在使用贪心算法之前,应该尝试证明该算法确实能得到最优解。如果无法证明,可能需要考虑其他算法,如动态规划。
- 算法复杂度:贪心算法通常具有较高的时间效率,但也要注意分析其时间复杂度和空间复杂度,确保在给定数据规模下算法是可接受的。
- 正确性测试:即使算法在理论上成立,也需要通过大量的测试样例来验证算法的正确性,尤其是边界条件和特殊情况的测试。