饭馆菜品选择问题|豆包MarsCode AI刷题

155 阅读4分钟

**一、**饭馆菜品选择问题

问题描述

小C来到了一家饭馆,这里共有 n 道菜,第 ii 道菜的价格为 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

二、解题思路

1、动态规划

实质上是选不选的问题,假设第i道菜选,则只需考虑第i-1道菜选不选的问题,若不选,则需考虑第i-1道菜选不选的问题。本问题还需考虑带不带蘑菇的问题。

2、状态定义

  • 设f[i][j][p]表示前i道菜选j道菜且带蘑菇的菜有p道的状态中的价格最小值
  1. 初始化

    • 使用 memset 将三维数组 f 初始化为一个较大的值(0x3f3f3f3f),表示初始状态为无穷大。
    • f[0][0][0] = 0,表示不选择任何菜时的总价格为0。
  2. 状态转移

    • 外层循环 i 遍历每一道菜。
    • 中层循环 j 遍历当前选择的菜的数量,范围为 0 到 min(i, k)
    • 内层循环 p 遍历当前选择的含有蘑菇的菜的数量,范围为 0 到 m
    • 如果当前菜含有蘑菇(s[i - 1] == '1'),则更新状态 f[i][j][p]为f[i - 1][j - 1][p - 1] + a[i]与f[i - 1][j][p]的最小值,确保 p > 0 且 j > 0
    • 如果当前菜不含有蘑菇(s[i - 1] == '0'),则更新状态 f[i][j][p]为f[i - 1][j - 1][p] + a[i]与f[i - 1][j][p]的最小值,确保 j > 0
  3. 返回结果

    • 如果 f[n][k][m] 仍然为初始值(0x3f3f3f3f),则返回 -1,表示无法满足条件。
    • 否则,返回 f[n][k][m],表示满足条件的最小总价格。

3、代码

#include <cstring>
#include <iostream>
#include <vector>


using namespace std;

long solution(const std::string &s, const std::vector<int> &a, int m, int k) {
  int n = s.size();
  int f[n + 1][k + 1][m + 1];
  memset(f, 0x3f, sizeof f);

  f[0][0][0] = 0;

  for (int i = 1; i <= n; i++) {
    for (int j = 0; j <= min(i, k); j++) {
      for (int p = 0; p <= m; p++) {
        if (s[i - 1] == '1') {
          f[i][j][p] = f[i - 1][j][p];
          if (p > 0 && j > 0)
            f[i][j][p] = min(f[i][j][p], f[i - 1][j - 1][p - 1] + a[i - 1]);
        } else {
          f[i][j][p] = f[i - 1][j][p];
          if (j > 0)
            f[i][j][p] = min(f[i][j][p], f[i - 1][j - 1][p] + a[i - 1]);
        }
      }
    }
  }

  int res = 0x3f3f3f3f;

  for (int i = 0; i <= m; i++)
    res = min(res, f[n][k][i]);

  if (res == 0x3f3f3f3f)
    return -1;
  return res;
}

int main() {
  std::cout << (solution("001", {10, 20, 30}, 1, 2) == 30) << std::endl;
  std::cout << (solution("111", {10, 20, 30}, 1, 2) == -1) << std::endl;
  std::cout << (solution("0101", {5, 15, 10, 20}, 2, 3) == 30) << std::endl;
  return 0;
}

四、时间复杂度分析

  1. 外层循环

    • i 从 1 到 n,共 n 次循环。
  2. 中层循环

    • j 从 0 到 min(i, k),最多 k 次循环。
  3. 内层循环

    • p 从 0 到 m,共 m + 1 次循环。

因此,总的时间复杂度为:

[ O(n * k * m) ]

空间复杂度分析

  1. 状态数组 f

    • f 是一个三维数组,大小为 n + 1 行,k + 1 列,m + 1 层。
    • 因此,空间复杂度为:

[ O((n + 1) * (k + 1) * (m + 1)) = O(n * k * m) ]

总结

  • 时间复杂度:( O(n * k * m) )
  • 空间复杂度:( O(n * k * m) )

优化

1、空间优化

-   由于每次状态转移只依赖于前一行的状态,可以使用滚动数组来优化空间复杂度。
-   可以将 `f` 数组从三维降到二维,空间复杂度优化为 ( O(k * m) )。
#include <iostream>
#include <vector>
#include <cstring>

using namespace std;

long solution(const std::string& s, const std::vector<int>& a, int m, int k) {
    int n = s.size();
    int f[k + 1][m + 1];
    memset(f, 0x3f, sizeof f);

    f[0][0] = 0;

    for(int i = 1; i <= n; i ++ ) {
        for(int j = min(i, k); j >= 0; j -- ) {
            for(int p = m; p >= 0; p -- ) {
                if(s[i - 1] == '1') {
                    if(p > 0 && j) f[j][p] = min(f[j][p], f[j - 1][p - 1] + a[i - 1]);
                }else {
                    if(j) f[j][p] = min(f[j][p], f[j - 1][p] + a[i - 1]);
                }
            }
        }
    }
    int res = 0x3f3f3f3f;

    for(int i = 0; i <= m; i ++ ) res = min(res, f[k][i]);

    if(res == 0x3f3f3f3f) return -1;
    return res;
}

int main() {
    std::cout << (solution("001", {10, 20, 30}, 1, 2) == 30) << std::endl;
    std::cout << (solution("111", {10, 20, 30}, 1, 2) == -1) << std::endl;
    std::cout << (solution("0101", {5, 15, 10, 20}, 2, 3) == 30) << std::endl;
    return 0;
}