和贪心、分治一样,动态规划是一种解题的思路,而不是一个具体的算法知识点。动态规划是一种需要学习才能获得的思维方法。像贪心、分治这样的方法,在生活中,或在其他学科中有很多类似的例子,很容易联想和理解。但是动态规划不是,它是一种生活中没有的抽象计算方法,没有学过的人很难自发产生这种思路。
硬币问题
假设有五种面值的 循环五次
#include<bits/stdc++.h>
using namespace std;
const int M = 251; //定义最大金额
const int N = 5; //5种硬币
int type[N] = {1, 5, 10, 25, 50}; //5种面值
int cnt[M]; //每个金额对应最少的硬币数量
const int INF = 0x1FFFFFFF;
void solve(){
for(int k = 0; k< M; k++) //初始值为无穷大
cnt[k] = INF;
cnt[0] = 0;
for(int j = 0; j < N; j++)
for(int i = type[j]; i < M; i++)
cnt[i] = min(cnt[i], cnt[i - type[j]] + 1); //递推式
}
int main(){
int s;
solve(); //计算出所有金额对应的最少硬币数量。打表
while(cin >> s){
if(cnt[s] == INF) cout <<"No answer" << endl;
else cout << cnt[s] << endl;
}
return 0;
}
DP(动态规划)的特征
- (重叠子问题)首先,子问题是原大问题的小版本,计算步骤完全一样;其次,计算大问题的时候,需要多次重复计算小问题。这就是“重叠子问题”。 一个子问题的多次计算,耗费了大量时间。用 DPDP 处理重叠子问题,每个子问题只需要计算一次,从而避免了重复计算,这就是 DPDP 效率高的原因。具体的做法是:首先分析得到最优子结构,然后用递推或者带记忆化搜索的递归进行编程,从而实现了高效的计算。
- (最优子结构)
最优子结构的意思是:首先,大问题的最优解包含小问题的最优解;其次,可以通过小问题的最优解推导出大问题的最优解。在硬币问题中,我们把 cnt[i]的计算,减小为 cnt[i-1] 的计算,也就是把原来为i的大问题,减小为 i-1的小问题,这是硬币问题的最优子结构。
第一题 走楼梯
import java.util.Scanner;
public class Main {
private static int n;
private static int[] dp;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
n = scanner.nextInt();
dp = new int[n + 1];
fun();
}
private static void fun() {
dp[1] = 1;
dp[2] = 2;
for (int i = 3; i <= n; i++) {
dp[i] = dp[i-1] +dp[i-2];
}
System.out.println(dp[n]);
return;
}
}
其实就是斐波那契数列
同理走楼梯可以扩展成多步走的方式,例如:
import java.util.Scanner;
public class Main {
private static int n;
private static int[] dp;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
n = scanner.nextInt();
dp = new int[n + 1];
fun();
}
private static void fun() {
dp[1] = 1;
dp[2] = 2;
dp[3] = 4;
for (int i = 4; i <= n; i++) {
dp[i] = dp[i-1] +dp[i-2] +dp[i-3];
}
System.out.println(dp[n]);
return;
}
}
第二题 最长公共子序列
样例输入
5 6
1 2 3 4 5
2 3 2 1 4 5
样例输出
4
可以分成两种情况讨论:
import java.util.Scanner;
public class Main {
private static int a;
private static int[] a1;
private static int[] b1;
private static int b;
private static int[][] dp;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
a = scanner.nextInt();
b = scanner.nextInt();
dp = new int[a + 1][b + 1];
a1 = new int[a+1];
b1 = new int[b+1];
for (int i = 1; i <= a; i++) {
a1[i] = scanner.nextInt();
dp[i][0] = 0;
}
for (int i = 1; i <= b; i++) {
b1[i] = scanner.nextInt();
dp[0][i] = 0;
}
for (int i = 1; i <= a; i++) {
for (int j = 1; j <= b; j++) {
if (a1[i] == b1[j]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
System.out.println(dp[a][b]);
}
}
第三题 最长上升子序列
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int[] a = new int[n];
int[] dp = new int[n];
for (int i = 0; i < n; i++) {
a[i] = scanner.nextInt();
dp[i] = 0;
}
dp[0] = 1;
for (int i = 1; i < n; i++) {
int max = 0;
for (int j = 0; j < i; j++) {
if (dp[j] > max && a[i] > a[j]) {
max = dp[j];
}
}
dp[i] = max +1;
}
int max = 0;
for (int i = 0; i < n; i++) {
if (dp[i] > max) {
max = dp[i];
}
}
System.out.println(max);
}
}
第四题 字符串转换
样例输入
abcf
bcfe
样例输出
2
把长度 m 的 A 存储在数组 a[1]∼a[m] ,长度为 n 的 B 存储在 b[1]∼b[n],然后定义二维数组 dp:dp[i][j]表示 A 的前 i 个字符转换 B 的前 j 个字符所需要的操作步骤,那么 dp[m][n] 就是答案。
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String s1 = scanner.next();
String s2 = scanner.next();
char[] c1 = s1.toCharArray();
char[] c2 = s2.toCharArray();
int[][] dp = new int[c1.length + 1][c2.length + 1];
for (int i = 0; i <= c1.length; i++) {
dp[i][0] = i;
}
for (int i = 0; i <= c2.length; i++) {
dp[0][i] = i;
}
for (int i = 1; i <= c1.length; i++) {
for (int j = 1; j <= c2.length; j++) {
if (c1[i-1] == c2[j-1]) {
dp[i][j] = dp[i - 1][j - 1];
} else {
dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + 1;
}
}
}
System.out.println(dp[c1.length][c2.length]);
}
}
第五题 完全背包
import java.util.Scanner;
public class Main {
private static int N;
private static int V;
private static int[] v;
private static int[] w;
private static int[][] dp;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
N = scanner.nextInt();
V = scanner.nextInt();
v = new int[N + 1];
w = new int[N + 1];
dp = new int[N + 1][V + 1];
for (int i = 1; i <= N; i++) {
w[i] = scanner.nextInt();//体积
v[i] = scanner.nextInt();//价值
}
for (int i = 1; i <= N; i++) {
for (int j = 1; j <= V; j++) {
if (i == 1) {
dp[i][j] = 0;
}
for (int k = 0; k * w[i] <= j; k++) {
dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - k * w[i]] + k * v[i]);
}
}
}
for (int i = 1; i <= N; i++) {
for (int j = 1; j <= V; j++) {
System.out.print(dp[i][j] + " ");
}
System.out.println();
}
System.out.println(dp[N][V]);
}
}
第六题 0-1背包简化版
样例输入
24
6
8
3
12
7
9
7
样例输出
0
import java.util.Scanner;
public class Main {
private static int V;
private static int N;
private static int[] w;
private static int[] dp;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
V = scanner.nextInt();
N = scanner.nextInt();
w = new int[N + 1];
dp = new int[V + 1];
for (int i = 1; i <= N; i++) {
w[i] = scanner.nextInt();
}
for (int i = 1; i <= N; i++) {
for (int j = V; j >= w[i]; j--) {
if (dp[j] < dp[j - w[i]] + w[i]) {//在一定范围内使得体积尽可能大,小了就换
dp[j] = dp[j - w[i]] + w[i];
}
}
}
System.out.println(V-dp[V]);
}
}
第七题 网格图上的DP
样例输入
6 6 3 3
样例输出
6