看 MarsCode 能否帮我通过 Hard 题目 | 豆包MarsCode AI刷题

106 阅读6分钟

问题链接

285.水果店果篮最小成本问题

样例解释

看完题目后,只有样例一有输入输出的解释,我们可以让 MarsCode 顺便解释下其他两个样例:

image.png

以样例 2 为例

输入:n = 5, m = 3, s = 2, a = [2, 3, 1, 4, 6]
输出:17

输入输出解释如下:

我们尝试将前两个水果(2, 3)和后三个水果(1, 4, 6)分别装成果篮:

  1. 将前两个水果(2, 3)装成一个果篮。

    • 最大体积 u = 3
    • 最小体积 v = 2
    • 水果数量 k = 2
    • 成本计算:k * ⌊(u + v) / 2⌋ + s = 2 * ⌊(3 + 2) / 2⌋ + 2 = 2 * 2 + 2 = 6
  2. 将后三个水果(1, 4, 6)装成另一个果篮。

    • 最大体积 u = 6
    • 最小体积 v = 1
    • 水果数量 k = 3
    • 成本计算:k * ⌊(u + v) / 2⌋ + s = 3 * ⌊(6 + 1) / 2⌋ + 2 = 3 * 3 + 2 = 11

总成本:6 + 11 = 17

给点思路提示

样例现在是看懂了,但好像还是没什么思路,隐隐感觉可能是动态规划的题,那现在只好寻求 MarsCode 给点思路提示。

这里摘取最核心的部分:

image.png

思路整理

状态定义

定义dp[i]为前i个水果打包成若干果篮的最小总成本

状态转移

由于要求果篮里的水果必须是连续的,因此我们可以假设一个分割点j,从 0 到 j 的水果被打包成若干个果篮,从 j+1i 的水果打包成一个果篮。这样我们就可以实现从nn + 1的状态转移,目标转化为找到那个最佳分割点 j

状态转移方程如下:dp[i] = min(dp[i], dp[j] + cost(j + 1, i))

  • i - j <= m:果篮里的水果数不能超过 m
  • cost(j + 1, i) 表示从 j+1i 的水果打包成一个果篮的成本

边界条件

dp[0] = 0,即没有水果时的成本为 0

最终结果

dp[n]

初版代码

public class Main {
    private static int cost(int left, int right, int s, int[] a) {
        int minVal = a[left], maxVal = a[right];
        for(int i = left; i <= right; i++) {
            minVal = Math.min(minVal, a[i]);
            maxVal = Math.max(maxVal, a[i]);
        }
        return (maxVal + minVal) / 2 * (right - left + 1) + s;
    }

    public static int solution(int n, int m, int s, int[] a) {
        // write code here
        int[] dp = new int[n + 1];
        for(int i = 1; i <= n; i++) {
            dp[i] = Integer.MAX_VALUE / 2;
        }
        for(int i = 0; i < n; i++) {
            for(int j = i; j >= 0 && j >= i - m + 1; j--) {
                dp[i + 1] = Math.min(dp[i + 1], dp[j] + cost(j, i, s, a));
            }
        }
        return dp[n];
    }

    public static void main(String[] args) {
        System.out.println(solution(6, 4, 3, new int[]{1, 4, 5, 1, 4, 1}) == 21);
        System.out.println(solution(5, 3, 2, new int[]{2, 3, 1, 4, 6}) == 17);
        System.out.println(solution(7, 4, 5, new int[]{3, 6, 2, 7, 1, 4, 5}) == 35);
    }
}

没有优化,我们交一个试试:

image.png

em... 给通过了,本来想着超时然后让 AI 再帮我们优化一下,这下尴尬了...

没事,过了也能优化!

代码优化

其实我们可以发现,cost 计算部分,进行了很多冗余计算,我们试试 MarsCode 能给我们提供哪些优化建议

image.png

OK,这里的核心就是利用滑动窗口进行预处理,我们看下 MarsCode 的预处理部分实现:

        // 预处理最大和最小体积
        int[][] maxVolume = new int[n][n];
        int[][] minVolume = new int[n][n];
        for (int i = 0; i < n; i++) {
            maxVolume[i][i] = a[i];
            minVolume[i][i] = a[i];
            for (int j = i + 1; j < n; j++) {
                maxVolume[i][j] = Math.max(maxVolume[i][j - 1], a[j]);
                minVolume[i][j] = Math.min(minVolume[i][j - 1], a[j]);
            }
        }

但我们其实可以发现,窗口大小完全没必要设置为 n,因为果篮最大容积是 m,我们只需要计算到 m 即可。

那这里我们就不浪费 API 重新生成了,直接开始上手!!!

最终的代码如下:

public class Main {

    public static int solution(int n, int m, int s, int[] a) {
        // write code here
        int[] dp = new int[n + 1];

        // 预处理最大和最小体积
        int[][] maxVolume = new int[n][n];
        int[][] minVolume = new int[n][n];
        for (int i = 0; i < n; i++) {
            maxVolume[i][i] = a[i];
            minVolume[i][i] = a[i];
            for (int j = i + 1; j < n && j < i + m; j++) {
                maxVolume[i][j] = Math.max(maxVolume[i][j - 1], a[j]);
                minVolume[i][j] = Math.min(minVolume[i][j - 1], a[j]);
            }
        }

        for(int i = 1; i <= n; i++) {
            dp[i] = Integer.MAX_VALUE / 2;
        }
        for(int i = 0; i < n; i++) {
            for(int j = i; j >= 0 && j >= i - m + 1; j--) {
                int k = i - j + 1;
                int cost = k * ((maxVolume[j][i] + minVolume[j][i]) / 2) + s;
                dp[i + 1] = Math.min(dp[i + 1], dp[j] + cost);
            }
        }
        return dp[n];
    }

    public static void main(String[] args) {
        System.out.println(solution(6, 4, 3, new int[]{1, 4, 5, 1, 4, 1}) == 21);
        System.out.println(solution(5, 3, 2, new int[]{2, 3, 1, 4, 6}) == 17);
        System.out.println(solution(7, 4, 5, new int[]{3, 6, 2, 7, 1, 4, 5}) == 35);
    }
}

总结

核心考点

最后,让 MarsCode 帮我们总结下这部分的核心考点:

这题的核心考点主要包括以下几个方面:

1. 动态规划(Dynamic Programming, DP)

  • 状态定义:使用 dp[i] 表示前 i 个水果打包成若干果篮的最小总成本。
  • 状态转移:通过遍历所有可能的分组方式,计算每个分组的成本,并更新 dp[i]
  • 初始化dp[0] = 0,表示没有水果时的成本为0。

2. 预处理技巧

  • 预处理最大和最小体积:通过预处理出每个位置的最大和最小体积,避免在每次计算 cost 时重复遍历果篮中的所有水果。
  • 二维数组:使用二维数组 maxVolume 和 minVolume 来存储从 i 到 j 的最大和最小体积,提高计算效率。

3. 滑动窗口

  • 滑动窗口技术:在计算 dp[i] 时,使用滑动窗口技术来维护当前果篮的最大和最小体积,避免重复计算。

4. 成本计算公式

  • 成本公式:理解并正确应用成本公式 cost = k * ⌊(u + v) / 2⌋ + s,其中 k 是果篮中水果的数量,u 是果篮中水果的最大体积,v 是果篮中水果的最小体积,s 是一个常数。

5. 边界条件处理

  • 边界条件:处理好边界条件,确保在计算 dp[i] 时不会越界,并且正确处理只有一个水果的情况。

6. 优化思路

  • 优化计算:通过预处理和滑动窗口技术,减少重复计算,提高代码的性能。

让 MarsCode 拷打下我们

image.png

MarsCode 拷打的内容太多了,我来简单总结一下:

  1. 对状态定义这一块还不能很好的进行切入。如果能想到正确的状态定义,后面的状态转移就显得很自然了。
  2. 代码细节方面:可以用Arrays.fill(dp, Integer.MAX_VALUE)进行数组初始化

试试能不能让他给我推荐一些题

image.png

hhh,好像不大妙

最后

总体来说,有了 MarsCode 辅助刷题,确实是不用担心样例什么看不懂的情况,没有思路的话,也可以试着让 AI 提供点思路,刷题体验会好一点(就这,我还能再刷几道 Hard)。

就是有时候想让他提供一丢丢启发式的思路即可,他把完整思路 + 完整代码都提供了,感觉有点不大妙。