动态规划算法通常用于求解具有某种最优性质的问题。在这类问题中,可能会有许多可行解。每一个解都对应于一个值,我们希望找到具有最优值的解。动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。与分治法不同的是,适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的。若用分治法来解这类问题,则分解得到的子问题数目太多,有些子问题被重复计算了很多次。如果我们能够保存已解决的子问题的答案,而在需要时再找出已求得的答案,这样就可以避免大量的重复计算,节省时间。我们可以用一个表来记录所有已解的子问题的答案。不管该子问题以后是否被用到,只要它被计算过,就将其结果填入表中。这就是动态规划法的基本思路。具体的动态规划算法多种多样,但它们具有相同的填表格式 。
最大子串和
给定一整型数列{a1,a2…,an},找出连续非空子串{ax,ax+1,…,ay},使得该子序列的和最大.
也可以用分治,将串分成两半,最大子串要么在左边,要么在右边,要么穿过中点横跨左右.
暴力解法:直接求出所以的子串和再找出最大.
O(n^3)
int a[105],n,maxSum=0;
void solve(){
for(int i=0;i<n;i++){
for(int j=i;j<n;j++){
int T=0;
for(int k=i;k<=j;k++)
T+=a[k];
if(T>maxSum)
maxSum=T;
}
}
}
改进的穷举:
O(n^2)
int a[105],n,maxSum=0;
void solve(){
for(int i=0;i<n;i++){
int T=0;
for(int j=i;j<n;j++){
T+=a[j];
if(T>maxSum)
maxSum=T;
}
}
}
动态规划:
O(n)
使用数组b[j]保存0-j的最大子串和
例如:对于a[]={-2,11,-4,13,-5,-2}
b[]={-2,11,7,20,15,13}
int a[105],n,maxSum=0;
void solve(){
int b[105];
b[0]=a[0];
for(int i=1;i<n;i++){
if(b[i-1]>0)
b[i]=a[i]+b[i-1];
else
b[i]=a[i];
if(b[i]>maxSum)
maxSum=b[i];
}
}
最长递增子序列
给定一个长度为N的数组,找出一个最长的单调递增子序列,子序列不一定连续,但初始顺序不能乱。
用d[i]表示0-i数组的最长子序列长度,则有递推方程:
在求d[i]时,找到i前面的所有j使得a[j]<a[i],如果j存在,那么d[i]=max{d[j]}+1;如果不存在,那么a[i]自己构成长度为1的递增子序列.
int list(int[] p,n){
if(n==1)
return 1;
int d[]=new int[n];
Arrays.fill(d,0);
d[0]=1;
for(int i=1;i<n;i++){
d[i]=1;
for(int j=0;j<i;j++){
if(p[j]<p[i]&&d[j]+1>d[i])
d[i]=d[j]+1;
}
}
int max=1;
for(int i=0;i<n;i++){
if(d[i]>max)
max=d[i];
}
return max;
}
0-1背包问题(acwing 2)
有n个物品,它们有各自的体积和价值,现有给定容量的背包,如何让背包里装入的物品具有最大的价值总和?
设d[i][j]表示将前i个物品放入剩余容量为j的背包可获得的最大价值.则:
d[i+1][j]=max{d[i][j],d[i][j-w[i+1]]+v[i+1]}
d[i][j-w[i+1]]+v[i+1]其实是查表操作,查一下前i个物品在剩余容量j-w[i+1]时的最大价值.
因为对于第i+1个物品,如果重量小于背包容量则可以选择放入或不放入,我们取最大值.
对于第0个物品(w[0]),我们可以计算出背包容量为0~C时的最大价值,如果w[0]>剩余容量,则没法放入,如果w[0]<=剩余容量,则必须放入.
对于第1,2,3…个物品,可以利用上面递推公式求出.
public int knapSack(int[] w, int[] v, int C) {//w:重量,v价值,C背包容量
int size = w.length;
if (size == 0) {
return 0;
}
int[][] dp = new int[size][C + 1];
//初始化第一行
//仅考虑容量为0-C的背包放第0个物品的情况
for (int i = 0; i <= C; i++) {
dp[0][i] = w[0] <= i ? v[0] : 0;
}
//填充其他行和列
for (int i = 1; i < size; i++) {
for (int j = 0; j <= C; j++) {
dp[i][j] = dp[i - 1][j];
if (w[i] <= j) {
dp[i][j] = Math.max(dp[i][j], v[i] + dp[i - 1][j - w[i]]);
}
}
}
return dp[size - 1][C];
}
完全背包问题(acwing 3)
有 N 种物品和一个容量是C 的背包,每种物品都有无限件可用。
第 i 种物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
数据范围
0<N,C≤1000
0<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
10
这里增加了每种物品有无限个的条件.
还是设dp[i][j]表示将前i个物品放入剩余容量为j的背包可获得的最大价值
设第i+1个物品放入k(0,1,2,3…)个
如果剩余容量j>=kw[i]
则dp[i+1][j]=max{ dp[i][j-kw[i]]+k *v[i]}
dp数组含义:dp[i][j]dp[i][j]=从编号为1~i的物品中挑选物品放入容量为j的背包中能得到的最大价值。注意:n种物品编号范围为1-n,0做作递推的起点。
初始条件:dp[0][0~W]=0;设第0个物品重量为0,价值为0;
递推公式:
dp[i][j]=max{dp[i−1][j−k∗w[i]]+k∗v[i]∣0≤k&k∗w[i]≤j}
结果:结果在dp[n][W]中
创建设置时默认初始化为0,所以不用手动设置dp[0][0~W]=0.
由于引入了重量为0价值为0的物品,所以dp第一维长度为P.length + 1,由于最大容量为T,需访问dp[i][T],所以第二维长度为T+1.
public static class Knapsack {
private static int[] P={5,8};
private static int[] V={5,7};
private static int T = 10;
private int[][] dp = new int[P.length + 1][T + 1];
@Test
public void solve3() {
for (int i = 0; i < P.length; i++){
for (int j = 0; j <= T; j++){
for (int k = 0; k * V[i] <= j; k++){
dp[i+1][j] = Math.max(dp[i+1][j], dp[i][j-k * V[i]] + k * P[i]);
}
}
}
System.out.println("最大价值为:" + dp[P.length][T]);
}
找零钱(LeetCode 322)
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
示例 1:
输入: coins = [1, 2, 5], amount = 11
输出: 3
解释: 11 = 5 + 5 + 1
示例 2:
输入: coins = [2], amount = 3
输出: -1
说明:
你可以认为每种硬币的数量是无限的。
分析:这里硬币面值不一定能构成倍数关系,所以不能用贪心法.