动态规划
概念
动态规划通常是解决最优化问题的一种方法,它的基本思想是通过将原问题分解为若干个子问题来简化问题的求解。一般地,如果一个问题可以用一个递归式来表示,并且该递归式中存在重叠子问题,那么我们就可以采用动态规划的方法来解决这个问题。动态规划的核心思想是记忆化搜索,即将已经求解过的子问题的结果记录下来,以便下次使用。
步骤
(1)确定状态:首先需要确定问题的状态,即问题的规模。
(2)转移方程:根据状态的定义,确定状态之间的转移关系。
(3)边界条件:确定状态的初始值,即边界条件。
(4)计算顺序:通常需要按照一定的计算顺序计算每个状态的值,以保证每个状态的值都是通过已经计算出来的状态值来计算得到的。
优缺点
动态规划的优点是可以有效地避免重复计算,从而提高算法的效率。它可以用于解决各种最优化问题、决策问题等。但是,动态规划的缺点是需要存储大量的状态信息,占用大量的空间。
动态规划经典问题
(1)斐波那契数列:给定一个整数n,求斐波那契数列的第n项。
(2)背包问题:有n个物品和一个容量为C的背包。每个物品有一个重量w和一个价值v,选择若干物品放入背包中,使得放入的物品总重量不超过C,且总价值最大。
(3)最长公共子序列:给定两个字符串s1和s2,求它们的最长公共子序列的长度。
(4)最长上升子序列:给定一个序列,找到其中最长的严格递增的子序列的长度。
01背包问题
给定一个容量为C的背包,和n件物品,每件物品有一个重量w[i]和一个价值v[i],求如何选择物品放入背包,使得背包内物品的总价值最大,且不超过背包的容量。
一个01背包问题的代码的一般步骤是:
- 定义状态:用二维数组dp[i][j]表示前i件物品在容量为j的背包中能达到的最大价值。
- 定义状态转移方程:对于第i件物品,有两种选择,放入或不放入背包。如果不放入背包,那么dp[i][j]等于dp[i-1][j];如果放入背包,那么dp[i][j]等于dp[i-1][j-w[i]] + v[i]。因此,dp[i][j]等于这两种选择中较大者。
- 初始化边界条件:当i=0时,表示没有物品,那么dp[0][j]等于0;当j=0时,表示背包容量为0,那么dp[i][0]等于0。
- 选择适当的遍历顺序:根据状态转移方程,需要从左上角开始,按行或按列遍历二维数组。
- 返回最终结果:根据问题的要求,返回dp[n][C]。
//定义一个方法,输入是物品的重量数组w,物品的价值数组v,和背包的容量C,输出是最大价值
public int knapsack(int[] w, int[] v, int C) {
//如果输入为空或长度不匹配,返回0
if (w == null || v == null || w.length != v.length) {
return 0;
}
//如果背包容量为0,返回0
if (C == 0) {
return 0;
}
//获取物品的数量n
int n = w.length;
//定义一个二维数组dp,dp[i][j]表示前i件物品在容量为j的背包中能达到的最大价值
int[][] dp = new int[n+1][C+1];
//初始化边界条件,当i=0或j=0时,dp[i][j]=0
for (int i = 0; i <= n; i++) {
dp[i][0] = 0;
}
for (int j = 0; j <= C; j++) {
dp[0][j] = 0;
}
//从第一件物品开始,遍历所有物品
for (int i = 1; i <= n; i++) {
//从第一单位容量开始,遍历所有容量
for (int j = 1; j <= C; j++) {
//如果第i件物品的重量大于当前容量,那么不能放入背包,dp[i][j]=dp[i-1][j]
if (w[i-1] > j) {
dp[i][j] = dp[i-1][j];
} else {
//否则,有两种选择,放入或不放入背包,dp[i][j]=max(dp[i-1][j], dp[i-1][j-w[i-1]] + v[i-1])
dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-w[i-1]] + v[i-1]);
}
}
}
//返回最终结果,即dp[n][C]
return dp[n][C];
}