参考文章:http://www.360doc.com/content/13/0601/00/8076359_289597587.shtml
https://www.topcoder.com/community/data-science/data-science-tutorials/dynamic-programming-from-novice-to-advanced/
简介
动态规划简称DP,一般基于递归或一个或多个初始状态。当前问题可以由之前的已经解决的子问题推导得出。
入门
以硬币为例,假设有面值为V=(1,3,5)的硬币若干,给出一个值如11,求出最少可以使用这三种硬币凑足11,答案很简单为3。以下使用动态规划的思想求解:
设d[i]表示凑够i元至少需要d[i]个硬币,以下进行递推:
i=0:没有小于0的硬币,故d[0]=0
i=1:此时只有面值为1的硬币可用,故d[1]=1
i=2:此时依然只有面值为1的硬币可用,拿起面值为1的硬币,此时只要再凑足2-1的硬币即可,即d[1]的值,故d[2]=d[2-1]+1=2
i=3:此时可用的硬币为(1,3)
情况1:拿起面值为3的硬币,此时只要再凑足3-3的硬币即可,即d[0]的值,故d[3]=d[3-3]+1=1
情况2:拿起面值为1的硬币,此时只要再凑足3-1的硬币即可,即d[2]的值,故d[3]=d[3-1]+1=3
故d[3]=min{d[3-3]+1,d[3-1]+1}=2
i=4:此时可用的硬币为(1,3)
情况1:拿起面值为3的硬币,此时只要再凑足4-3的硬币即可,即d[1]的值,故d[3]=d[4-3]+1=2
情况2:拿起面值为1的硬币,此时只要再凑足4-1的硬币即可,即d[3]的值,故d[3]=d[4-1]+1=2
故d[4]=min{d[4-3]+1,d[4-1]+1}=2
总体思路为:当要求解d[i]时,先看V中可候选的元素[v1,v2,...,vi],针对每一个选取的vi,都可以得到一个子问题d[i-vi],显然这个子问题已经求解过了,因此d[i]=min{d[i-vi]+1}。
(PS: d[i-vi]+1中的加1表示选取了一个vi,因此总数等于子问题的值加1)
代码如下:
package AlgorithmsBases; public class DynamicProgramming_01 { public static void main(String[] args) { // TODO Auto-generated method stub //有三种面值的硬币 int[] V = new int[] {1,3,5}; System.out.println("result:"+getBinNum(4,V)); } //value表示总值,V表示硬币面值,返回最少需要的硬币数目 public static int getBinNum(int value,int[] V){ int[] Min = new int[value+1]; Min[0]=0; //除了Min[0]以外,其余先设为最大值 for(int i=1;i<Min.length;i++){ Min[i]=Integer.MAX_VALUE; } //以下为核心代码 //Min[i]表示凑足i需要的硬币数 for(int i=0;i<=value;i++){ //V表示现有的各个面值的硬币 for(int j=0;j<V.length;j++){ System.out.println(V[j]+":"+i); if(V[j]<=i && Min[i-V[j]]+1<Min[i]){ Min[i]=Min[i-V[j]]+1; } } } return Min[value]; } }
初级
接下来讨论一个经典问题:找出一个序列的最长非降子序列的长度(longest non-decreasing sequence)
比如V={5,3,4,8,6,7}的非降最长子序列为{3,4,6,7},长度为4
设d[i]表示V中第i个元素对应的最长非降子序列的长度,求解思路如下:
i=1,V[i]=5:此时V[1]前面没有值,因此d[1]=1
i=2,V[i]=3:此时V[1:i-1]的元素中没有小于等于V[2]=3的值,因此d[2]=1
i=3,V[i]=4:此时V[1:i-1]的元素中小于等于V[3]的元素有{V[2]=3},因此d[3]=d[2]+1=2
i=4,V[i]=8:此时V[1:i-1]的元素中小于等于V[4]的元素有{V[2]=3,V[3]=4},因此d[4]=max{d[2]+1,d[3]+1}=mxa{2,3}=3
代码如下:
package AlgorithmsBases; public class DynamicProgramming_02 { public static void main(String[] args) { // int[] V = new int[] {5,3,4,8,6,7}; System.out.println("result:"+getLongestNonDecreasingSequence(V)); } //以下代码实现输入一个数组,输出最长非降子序列的长度 //比如{5,3,4,8,6,7}的非降最长子序列为{3,4,6,7},长度为4 public static int getLongestNonDecreasingSequence(int[] V){ int[] MaxLen = new int[V.length]; //MaxLen最小值为1 for(int i=0;i<MaxLen.length;i++){ MaxLen[i]=0; } for(int i=0;i<V.length;i++){ //以下代码求解d[i]=max{1,d[j]+1},其中j<=i,V[j]<=V[i] int maxb=1; for(int j=0;j<=i;j++){ if(V[j]<=V[i] && MaxLen[j]+1>=maxb) maxb=MaxLen[j]+1; } MaxLen[i]=maxb; } int max=0; for(int k=0;k<MaxLen.length;k++){ if(max<MaxLen[k]) max=MaxLen[k]; } printArray(MaxLen); return max; } public static void printArray(int[] maxLen){ System.out.print("printArray:"); for(int i=0;i<maxLen.length;i++){ System.out.print(maxLen[i]+" "); } System.out.println(""); } }
中级
有一个M*N的方格,每个格子都有一定数量的苹果,用V矩阵表示,从左上角出发,每次只能右移一步或下移一步,求解能获得的最大苹果数目。
这个问题属于二维动态规划,但思想与前面的问题类似。
设dp[i][j]表示我们走到第i行第j列的方格时能获得的最大苹果数目,则dp[i][j] = V[i][j] + max{dp[i-1][j],dp[i][j-1]}
代码如下:
package AlgorithmsBases; public class DynamicProgramming_03 { public static void main(String[] args) { // int[][] V = new int[][] {{1,2,1,1,2},{2,1,2,2,1},{1,1,2,2,1},{2,2,1,1,1},{1,1,2,1,2},{1,1,2,1,2}}; //printArray(V); System.out.println("result:"+getLongestNonDecreasingSequence(V)); } public static int getLongestNonDecreasingSequence(int[][] V){ int rows = V.length; int cols = V[0].length; int[][] dp = new int[rows][cols]; for(int i=0;i<rows;i++){ for(int j=0;j<cols;j++){ if(i>0 && j>0){ dp[i][j]=V[i][j]+(dp[i-1][j]>dp[i][j-1]?dp[i-1][j]:dp[i][j-1]); } if(i>0&&j<=0){ dp[i][j]=V[i][j]+dp[i-1][j]; } if(i<=0&&j>0){ dp[i][j]=V[i][j]+dp[i][j-1]; } if(i<=0&&j<=0){ dp[i][j]=V[i][j]; } } } int max=0; for(int i=0;i<rows;i++){ for(int j=0;j<cols;j++){ if(max<=dp[i][j]){ max=dp[i][j]; } } } return max; } public static void printArray(int[] V){ System.out.print("printArray:"); for(int i=0;i<V.length;i++){ System.out.print(V[i]+" "); } System.out.println(""); } public static void printArray(int[][] V){ System.out.println("printArray:"); int rows = V.length; int cols = V[0].length; for(int i=0;i<rows;i++){ for(int j=0;j<cols;j++){ System.out.print(V[i][j]+" "); } System.out.println(""); } System.out.println(""); } }