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

142 阅读8分钟

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

饭馆菜品选择问题

问题描述

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

小C希望点 kk 道菜,且希望总价格尽可能低。由于她不喜欢蘑菇,她希望所点的菜中最多只有 mm 道菜含有蘑菇。小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

解决思路

这题可以使用动态规划来进行求解

对于状态的定义,f[i][j][p] 表示,前 i 道菜中,选择 j 道菜,这 j 道菜中有 p 个蘑菇的情况下的最小总价格

初始状态 f[0][0][0] = 0 表示没有菜品,且不进行选择,因此总价格为0

对于状态转移,每一道菜 i

如果我们选择这一道菜,菜中有蘑菇(s[i - 1] = 1),则有 f[i][j][p] = min(f[i][j][p], f[i - 1][j - 1][p - 1] + a[i - 1])

如果我们选择这一道菜,菜中没有蘑菇(s[i - 1] = 0),则有 f[i][j][p] = min(f[i][j][p], f[i - 1][j - 1][p] + a[i - 1])

如果我们不选这一道菜,则有 f[i][j][p] = f[i - 1][j][p]

最后的结果为 f[n][k][0] ~ f[n][k][m] 的最小值

AI验证思路

我一开始的想法是动态规划,尝试使用2维dp进行求解,但是感觉不太好想,于是再加入1维,变成3维dp后可以对蘑菇进行控制,就方便了许多。但是让AI给我生成解决思路的时候是另一种方法,排序 + 贪心的方法,确实这个方法会方便一点,使用动态规划可能做复杂了,中等的题目应该不会出现3维的动态规划,但是既然想到了这个方向,就尝试着继续进行,我直接限制AI往动态规划的方向进行尝试,确实可以解出来

我对AI生成动态规划的思路进行过多次尝试,不断开启新对话,让AI给出动态规划的思路,生成的结果大概有两种,可能是2维dp,也可能是3维的dp,不过AI生成的2维dp是错误的,3维dp可以正确使用。使用2维dp来描述题目的状态确实不太够,AI也做错了。

Prompt

这道题用动态规划的思路要怎么做

MarsCode AI(它有可能给出另一种2维dp的思路,不过是错的)

这道题可以使用动态规划来解决。动态规划的核心思想是将问题分解为子问题,并通过解决子问题来构建最终的解决方案。

解题思路
  1. 定义状态

    • 设 dp[i][j][m] 表示在前 i 道菜中选择 j 道菜,并且最多有 m 道菜含有蘑菇时的最小总价格。
  2. 状态转移方程

    • 对于每一道菜 i,我们有两种选择:选或不选。

    • 如果不选第 i 道菜,则状态转移为 dp[i][j][m] = dp[i-1][j][m]

    • 如果选第 i 道菜,则需要根据第 i 道菜是否含有蘑菇来更新状态:

      • 如果第 i 道菜含有蘑菇(s[i-1] == '1'),则状态转移为 dp[i][j][m] = min(dp[i][j][m], dp[i-1][j-1][m-1] + a[i-1])
      • 如果第 i 道菜不含有蘑菇(s[i-1] == '0'),则状态转移为 dp[i][j][m] = min(dp[i][j][m], dp[i-1][j-1][m] + a[i-1])
  3. 初始化

    • dp[0][0][0] = 0,表示不选任何菜时的总价格为 0。
    • 其他状态初始化为一个较大的值(如 float('inf')),表示这些状态在初始时是不可达的。
  4. 最终结果

    • 最终答案为 dp[n][k][m],即在前 n 道菜中选择 k 道菜,并且最多有 m 道菜含有蘑菇时的最小总价格。
    • 如果 dp[n][k][m] 仍然是初始值(如 float('inf')),则表示无法满足条件,输出 -1
数据结构选择
  • 使用三维数组 dp 来存储状态。
算法步骤
  1. 初始化 dp 数组。
  2. 遍历每一道菜,更新 dp 数组。
  3. 返回 dp[n][k][m] 作为最终结果。

通过上述思路,你可以逐步实现动态规划的代码。

代码实现

根据AI给出的思路

def solution(s: str, a: list, m: int, k: int) -> int:
    n = len(s)
    if n < k or m < 0:
        return -1

    # 初始化DP数组
    f = [[[float('inf')] * (m + 1) for _ in range(k + 1)] for _ in range(n + 1)]
    f[0][0][0] = 0

    # 更新DP数组
    for i in range(1, n + 1):
        for j in range(min(i, k) + 1):
            for p in range(m + 1):
                if s[i - 1] == '1':
                    f[i][j][p] = f[i - 1][j][p]
                    if p > 0 and 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])

    # 获取结果
    res = float('inf')
    for i in range(m + 1):
        res = min(res, f[n][k][i])

    return -1 if res == float('inf') else res

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)

复杂度分析

代码中最复杂的地方就是dp数组的更新,其中采用了三重循环,通过观察可以看出时间复杂度为 O(nkm)O(n * k * m)

对于空间复杂度,dp数组也是三维的 O(nkm)O(n * k * m)

接下来看看AI的分析结果如何

Prompt

可以帮我分析一下编辑区代码的复杂度吗

MarsCode AI

当然可以帮你分析一下编辑区代码的时间复杂度和空间复杂度。

时间复杂度分析

你的代码使用了三重循环来更新动态规划(DP)数组。具体来看:

  1. 外层循环:遍历每一道菜,循环次数为 n
  2. 中层循环:遍历选择的菜的数量,循环次数最多为 k
  3. 内层循环:遍历含有蘑菇的菜的数量,循环次数最多为 m

因此,三重循环的总次数为 n * k * m

for i in range(1, n + 1):
    for j in range(min(i, k) + 1):
        for p in range(m + 1):
            # 更新DP数组

所以,时间复杂度为 O(n * k * m)

空间复杂度分析

你的代码使用了一个三维的DP数组 f,其大小为 (n + 1) * (k + 1) * (m + 1)

f = [[[float('inf')] * (m + 1) for _ in range(k + 1)] for _ in range(n + 1)]

因此,空间复杂度为 O((n + 1) * (k + 1) * (m + 1)),即 O(n * k * m)

总结
  • 时间复杂度:O(n * k * m)
  • 空间复杂度:O(n * k * m)
优化建议
  1. 时间复杂度

    • 由于时间复杂度已经是 O(n * k * m),在 nkm 都不大的情况下,这个复杂度是可以接受的。
    • 如果 nkm 非常大,可以考虑优化算法,例如使用更高效的贪心算法或剪枝策略。
  2. 空间复杂度

    • 空间复杂度也可以优化,例如使用滚动数组(rolling array)来减少空间占用。
    • 滚动数组可以将空间复杂度从 O(n * k * m) 优化到 O(k * m)。
滚动数组优化示例
def solution(s: str, a: list, m: int, k: int) -> int:
    n = len(s)
    if n < k or m < 0:
        return -1

    # 初始化DP数组
    f = [[float('inf')] * (m + 1) for _ in range(k + 1)]
    f[0][0] = 0

    # 更新DP数组
    for i in range(1, n + 1):
        for j in range(k, 0, -1):  # 从后往前更新,避免覆盖
            for p in range(m, -1, -1):  # 从后往前更新,避免覆盖
                if s[i - 1] == '1':
                    if p > 0:
                        f[j][p] = min(f[j][p], f[j - 1][p - 1] + a[i - 1])
                else:
                    f[j][p] = min(f[j][p], f[j - 1][p] + a[i - 1])

    # 获取结果
    res = float('inf')
    for i in range(m + 1):
        res = min(res, f[k][i])

    return -1 if res == float('inf') else res

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)
总结
  • 时间复杂度:O(n * k * m)
  • 空间复杂度:O(k * m)

通过使用滚动数组,空间复杂度得到了显著优化。

关于这道题

对于解题的流程,首先是自己想好一个解决方案,再与AI进行讨论,如果你比较厉害,可以自己想出解决思路,那确实很好了,如果你不够强大,无法自己得出解决思路,那也不必担心,我们都可以和AI进行进一步的深入交流,完善解决问题的思路。

有了思路以后开始实现,同样的,不管你能不能写的出来,你都应该先去尝试,之后再与AI讨论,这样不仅你获得了进步,你要解决的问题也会在AI的帮助下变得更加完美。

你对问题的理解有多深刻,你就能创造出多强大的Prompt,AI的回答也会与你的理解匹配,以上就是我的理解和工作内容。