这道题是一道经典的动态规划问题,可以转化为“01背包”模型来求解。

题目核心思路分析

我们需要雇佣付费油漆匠和免费油漆匠来刷完 n 堵墙,目标是让总开销最小。
*   付费油漆匠刷第 i 堵墙,花费 cost[i],耗时 time[i]。
*   免费油漆匠刷任意墙,花费 0,耗时 1。
*   关键限制:免费油漆匠必须在付费油漆匠工作时才能工作。

这意味着,如果我们雇佣付费油漆匠去刷第 i 堵墙,他不仅完成了这 1 堵墙,同时他工作的 time[i] 这段时间,还可以让免费油漆匠顺手刷完 time[i] 堵其他的墙。
因此,雇佣付费油漆匠刷第 i 堵墙的“实际收益”是完成了 time[i] + 1 堵墙(1堵自己刷的 + time[i]堵免费刷的)。

问题就转化为了一个01背包问题:
*   背包容量:需要刷的墙的总数 n。
*   物品:每一堵墙都可以看作一个物品。
*   物品体积:雇佣付费油漆匠刷第 i 堵墙能覆盖的墙数,即 time[i] + 1。
*   物品价值(开销):雇佣付费油漆匠刷第 i 堵墙的成本,即 cost[i]。
*   目标:在覆盖至少 n 堵墙的前提下,使得总开销最小。

Java 动态规划实现

我们可以使用一维数组来进行空间优化的动态规划(滚动数组)。

class Solution {
    public int paintWalls(int[] cost, int[] time) {
        int n = cost.length;
        // dp[j] 表示完成至少 j 堵墙所需的最小开销
        // 初始化为一个较大的值,防止加法溢出可以使用 Integer.MAX_VALUE / 2
        int[] dp = new int[n + 1];
        java.util.Arrays.fill(dp, Integer.MAX_VALUE / 2);
        // 完成 0 堵墙的开销为 0
        dp[0] = 0;

        // 遍历每一堵墙(即遍历每一个物品)
        for (int i = 0; i < n; i++) {
            int c = cost[i];
            // 当前付费油漆匠刷这面墙,实际能覆盖的墙数(体积)
            int t = time[i] + 1; 
            
            // 01背包倒序遍历,防止同一面墙被重复计算
            for (int j = n; j >= 0; j--) {
                // 状态转移:
                // 1. 不雇佣付费油漆匠刷第 i 堵墙:开销保持 dp[j] 不变
                // 2. 雇佣付费油漆匠刷第 i 堵墙:开销为 dp[上一状态] + c
                // 上一状态需要覆盖的墙数是 j - t,如果 j - t < 0,说明这面墙足以覆盖所有剩余需求,取 dp[0] 即可
                dp[j] = Math.min(dp[j], dp[Math.max(0, j - t)] + c);
            }
        }
        
        // 返回完成至少 n 堵墙的最小开销
        return dp[n];
    }
}

复杂度分析
*   时间复杂度:O(N²),其中 N 是墙的数量。我们需要遍历 N 个物品,内层循环遍历容量 N。
*   空间复杂度:O(N),使用了一个长度为 N+1 的一维 dp 数组。

 

更多推荐