目录
0-1背包问题
0 - 1 背包问题是一个经典的组合优化问题。
问题描述:给定一组物品,每个物品有一定的价值和重量,以及一个背包,背包有一定的容量限制。要求在不超过背包容量的前提下,选择物品放入背包,使得背包中物品的总价值最大。
示例:假设有三个物品,物品 1 价值 6,重量 2;物品 2 价值 10,重量 3;物品 3 价值 12,重量 5,背包容量为 5。
常见解法:
- 动态规划:创建一个二维数组
dp[i][j]
,其中i
表示物品索引,j
表示背包容量。通过状态转移方程计算每个状态下的最优值。- 状态转移方程:
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i])
,当j >= weight[i]
。
- 状态转移方程:
- 回溯法:通过递归地尝试选择或不选择每个物品,来搜索所有可能的组合,找到最优解。但这种方法在物品数量较多时效率较低。
特点:
- 每个物品只能选择一次(0 - 1 选择),要么放入背包,要么不放入。
- 物品的选择具有互斥性。
应用场景:
- 资源分配:例如在有限的资源下分配项目、任务等。
- 投资决策:在有限的资金下选择投资项目。
思路
这里把每堵墙看作一个物品,刷这堵墙的开销
cost[i]
类似于物品的价值,付费油漆匠刷这堵墙所需的时间time[i]
类似于物品的重量(需要免费油漆匠相应的工作时间)。而memo
数组用于记录已经计算过的不同状态(已考虑的墙的数量和免费油漆匠已工作的时间)下的最小开销,避免重复计算,从而提高效率。通过递归地考虑选择或不选择当前墙,逐步求解出刷完所有墙的最小开销。
具体来说,对于每堵墙(物品),有两种选择:选择让付费油漆匠刷这堵墙,那么需要消耗相应的时间(类似于背包中放入物品占用的重量),同时加上这堵墙的开销,然后基于剩余的墙和免费油漆匠的剩余工作时间继续递归求解;或者不选择让付费油漆匠刷这堵墙,直接基于前一堵墙的情况继续递归求解。最终通过比较两种选择的结果,取开销较小的作为当前状态的最优解,并记录在
memo
数组中。这样,在后续遇到相同状态时,可以直接使用之前计算得到的结果,避免重复计算。
解题方法
- 定义了一个二维数组
memo
用于记忆化搜索,避免重复计算已经求解过的子问题的结果。 paintWalls
方法是主要的入口函数,它调用dfs
方法进行深度优先搜索。dfs
方法接受以下参数:i
:表示当前考虑的墙的索引。j
:表示免费油漆匠已经工作的时间。cost
:表示每堵墙的开销数组。time
:表示每堵墙付费油漆匠所需的时间数组。memo
:记忆化数组。
- 首先进行边界情况的判断:
- 如果
j > i
,意味着免费油漆匠工作的时间已经超过了墙的数量,此时开销为0,直接返回。 - 如果
i < 0
,返回一个较大的值(这里使用了Integer.MAX_VALUE / 2
),表示这种情况下的开销是不合理的大。
- 如果
- 计算当前状态在
memo
数组中的索引k
,通过j + memo.length
得到。 - 如果
memo[i][k]
不为-1
,说明之前已经计算过这个子问题,直接返回记忆的值。 - 否则,进行递归计算:
res1 = dfs(i - 1, j + time[i], cost, time, memo) + cost[i]
:表示选择让付费油漆匠刷当前这堵墙(i
),那么免费油漆匠会工作time[i]
时间,然后递归计算前i - 1
堵墙的最小开销,并加上刷当前墙的开销cost[i]
。res2 = dfs(i - 1, j - 1, cost, time, memo)
:表示不让付费油漆匠刷当前这堵墙(i
),免费油漆匠工作时间减1,递归计算前i - 1
堵墙的最小开销。
- 取
res1
和res2
中的较小值作为当前状态的最小开销,并将其存储到memo[i][k]
中,以便后续直接使用。 - 最终返回计算得到的最小开销。
时间复杂度
O(n²)
空间复杂度
O(n²)
Code
import java.util.Arrays;
public class Solution {
public int paintWalls(int[] cost, int[] time) {
int n = cost.length;
int[][] memo = new int[n][n * 2 + 1];
for (int[] row : memo) {
Arrays.fill(row, -1);
}
return dfs(n - 1, 0, cost, time, memo);
}
private int dfs(int i, int j, int[] cost, int[] time, int[][] memo) {
if (j > i) {
return 0;
}
if (i < 0) {
return Integer.MAX_VALUE / 2;
}
int k = j + memo.length;
if (memo[i][k]!= -1) {
return memo[i][k];
}
int res1 = dfs(i - 1, j + time[i], cost, time, memo) + cost[i];
int res2 = dfs(i - 1, j - 1, cost, time, memo);
int min = Math.min(res1, res2);
memo[i][k] = min;
return min;
}
}