这里是申申如也,一个把编程当成爱好的程序员,期待与你一起进步!
文章目录
动态规划
分析
最优子结构
:不管子问题多小,此时都是最优解。当这些子问题合并就会得到总问题。
重叠子问题
:我们将各种最优结构记录在表中,因为之后的问题可能都会用到(看下文慢慢理解)
备忘录方法
:即将重叠的问题记录在表中。
解题流程
- 将原问题分解为子问题,并保存子问题的解
- 确定状态:用动态规划解题时,我们往往将和子问题相关的各个变量的一组取值,称之为一个“状 态”。
- 确定初始值.
- 确定状态转移方程
然后看题
0-1背包问题
有n个物品,它们有各自的体积和价值,现有给定容量的背包,如何让背包里装入的物品具有最大的价值总和?
子问题
从背包容量为0开始,从只装一个物品开始,只要是最能装的(即重量小,价值大)。就是一个最优的子问题,然后看能不能合并,很明显这是可以合并的,我可以从小背包少物品开始一步步到大背包多物品。
确定状态
只有两种,装还是不装。
装了之后包里的物品价值总和是不是最大,不装之前容量是如何的(能不能装进去)都决定我们能不能装这个物品。
那我们如何判断呢?
当然是价值大重量小的优先。
初始化
物品重量价值初始化
编号 | 重量 | 价值 |
---|---|---|
1 | w1=2 | v1=6 |
2 | w2=2 | v2=3 |
3 | w3=6 | v3=5 |
4 | w4=5 | v4=4 |
5 | w5=4 | v5=6 |
int[] w = {2,2,6,5,4};
int[] v = {6,3,5,4,6};
物品数量
:n=5
背包容量
:sw=10
确定状态转移方程
我们在面对一个物品要不要装进去的时刻,要面对两个问题
- 能不能装进去
- 装进去之后是不是价值最高的
if(j<w[i])
dp[i][j]=dp[i-1][j];
else
dp[i][j]=Math.max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);
我们看这个代码。
j
:背包容量
w[i]
:某一个物品的重量
dp
:备忘录
首先判断能不能装,不能装那么我们这时的背包中的物品价值和保持不变,即dp[i][j]=dp[i-1][j];
能装的话,我们取装与不装之间的最大值
dp[i][j]=Math.max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);
完整代码
public static void main(String[] args) {
int n = 5;
int[] w = {2, 2, 6, 5, 4};
int[] v = {6, 3, 5, 4, 6};
int sw = 10;
int[][] dp = new int[n][sw + 1];
for (int i = 0; i <= sw; i++) {
if (i < w[0]) {
dp[0][i] = 0;
} else {
dp[0][i] = v[0];
}
}
for (int i = 1; i < n; i++) {
for (int j = 0; j <= sw; j++) {
if (j < w[i]) {
dp[i][j] = dp[i - 1][j];
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i]);
}
}
}
System.out.println(dp[n - 1][sw]);
}
备忘录与流程图
时间复杂度
在这串代码中,决定时间复杂度的是
for (int i = 1; i < n; i++) {
for (int j = 0; j <= sw; j++) {
if (j < w[i]) {
dp[i][j] = dp[i - 1][j];
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i]);
}
}
}
很明显复杂度只体现在自己所做的备忘录中,在备忘录中进行遍历的时间复杂度:O(n*sw)
测试与答案
15
最长公共子序列问题
给定两个字符串,计算两个字符串的最大公共子串的长度。
子问题
每一个字母相同
确定状态
相同或者不相同
初始化
确定两个字符串text1
、text2
及其长度。
确定状态转移方程
首先判断两个字母是否一样,一样的话就计数长度加一,不一样的话长度不变。
for (int j = 1; j <= n; j++) {
char c2 = text2.charAt(j - 1);
if (c1 == c2) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
完整代码
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String text1 = sc.next();
String text2 = sc.next();
int ans = longestCommonSubsequence(text1, text2);
System.out.print(ans);
}
public static int longestCommonSubsequence(String text1, String text2) {
int m = text1.length(), n = text2.length();
int[][] dp = new int[m + 1][n + 1];
for (int i = 1; i <= m; i++) {
char c1 = text1.charAt(i - 1);
for (int j = 1; j <= n; j++) {
char c2 = text2.charAt(j - 1);
if (c1 == c2) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
return dp[m][n];
}
备忘录与流程图
时间复杂度
同上,此时为O(n*m)
测试与答案
石子合并问题
有N堆石子排成一排(n<=100),现要将石子有次序地合并成一堆,规定每次只能选相邻的两堆合并成一堆,并将新的一堆的石子数,记为该次合并的得分,编一程序,给出堆数n及每堆石子数(<=200);
(1)选择一种合并石子的方案,使得做n-1次合并,得分的总和最少
(2)选择一种合并石子的方案,使得做n-1次合并,得分的总和最多
子问题
每次合并即为一个子问题
确定状态
每次合并所得出来的值,在这题中有两个状态,最大值与最小值。
状态转移方程
我们先找到最大值与最小值,并进行比较,比较之后进行覆盖。
for (int k = i + 1; k < j; k++) {
int t = mm[i][k] + mm[k + 1][j] + sum(i, j);
if (t < mm[i][j]) {
mm[i][j] = t;
}
if (t > MM[i][j]) {
MM[i][j] = t;
}
}
完整代码
public class Stone_merge {
static int[] aa;
static int[][] mm;
static int[][] MM;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
aa = new int[n];
for (int i = 0; i < n; i++) {
aa[i] = sc.nextInt();
}
mm = new int[n + 1][n + 1];
MM = new int[n + 1][n + 1];
solve(n);
System.out.println(mm[1][n]);
System.out.println(MM[1][n]);
}
private static void solve(int n) {
for (int i = 1; i <= n; i++) {
mm[i][i] = 0;
MM[i][i] = 0;
}
for (int r = 2; r <= n; r++) {
for (int i = 1; i <= n - r + 1; i++) {
int j = i + r - 1;
mm[i][j] = mm[i][i] + mm[i + 1][j] + sum(i, j);
MM[i][j] = MM[i][i] + MM[i + 1][j] + sum(i, j);
for (int k = i + 1; k < j; k++) {
int t = mm[i][k] + mm[k + 1][j] + sum(i, j);
if (t < mm[i][j]) {
mm[i][j] = t;
}
if (t > MM[i][j]) {
MM[i][j] = t;
}
}
}
}
}
private static int sum(int i, int j) {
int sum = 0;
for (int z = i - 1; z <= j - 1; z++) {
sum += aa[z];
}
return sum;
}
}
备忘录与流程图
时间复杂度
O(n^2)