1. 什么是动态规划?与递归有什么区别?
先说递归:递归——程序调用自身,也就是函数自己调用自己。递归通常从顶部将问题分解,通过解决掉所有分解出来的小问题,来解决整个问题;
斐波那契数列 f(n) = f(n-1) + f(n-2) 从上往下开始算 从n开始 直至找到n=1 n=2的递归出口
动态规划——通常与递归相反,其从底部开始解决问题。将所有小问题解决掉,进而解决的整个问题。
2、动态规划(Dynamic Programming)
一、基本概念
动态规划过程是:每次决策依赖于当前状态,又随即引起状态的转移。一个决策序列就是在变化的状态中产生出来的,所以,这种多阶段最优化决策解决问题的过程就称为动态规划。
二、基本思想与策略
基本思想与分治法类似,也是将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。依次解决各子问题,最后一个子问题就是初始问题的解。
由于动态规划解决的问题多数有重叠子问题这个特点,为减少重复计算,对每一个子问题只解一次,将其不同阶段的不同状态保存在一个二维数组中。
与分治法最大的差别是:适合于用动态规划法求解的问题,经分解后得到的子问题往往不是互相独立的(即下一个子阶段的求解是建立在上一个子阶段的解的基础上,进行进一步的求解)。
什么题适合用DP解?
使用动态规划特征:
1. 求一个问题的最优解
2. 大问题可以分解为子问题,子问题还有重叠的更小的子问题
3. 整体问题最优解取决于子问题的最优解(状态转移方程)
4. 从上往下分析问题,从下往上解决问题
5. 讨论底层的边界问题DP 一般用一个数组或者二维数组 存储每一次递归过程 或子问题产生的结果
1、构造问题所对应的过程。
2、思考过程的最后一个步骤,看看有哪些选择情况。
3、找到最后一步的子问题,确保符合“子问题重叠”,把子问题中不相同的地方设置为参数。
4、使得子问题符合“最优子结构”。
5、找到边界,考虑边界的各种处理方式。
6、确保满足“子问题独立”,一般而言,如果我们是在多个子问题中选择一个作为实施方案,而不会同时实施多个方案,那么子问题就是独立的。
7、考虑如何做备忘录。
8、分析所需时间是否满足要求。
9、写出转移方程式。
1. 剑指offer ---剪绳子
给你一根长度为N的绳子,请把绳子剪成M段(m,n都是整数 都大于1),每段绳子的
长度记为k[0],k[1],k[2]…. 请问如何剪绳子使得k[0],k[1],k[2]
的乘积最大
例如 绳子长度8 最大乘积18 = 2*3*3分析: 求最优 有没有重叠的子问题 ,先剪一刀 i n-i (n-i这部分还可以再分)
f(n) = max( f(i)*f(n-i) ) 这里f(n)长度为n所求的最大乘积 显然需要一个数组去存储 max里面的结果 选择最佳(不同的i对应不同的结果 需要遍历取不同的i)
一般先从最下面开始比如 n=0 n=1 这里 n>1 n=2
代码如下:
public class Main { public static int getMax(int length){ if(length < 2){ return 0; } if(length==2) return 1; if(length==3) return 2; int dp[]=new int[length+1]; dp[0]=0; dp[1]=1; dp[2]=2; dp[3]=3; int max=0; for(int i=4; i <= length; i++){ max=0; for(int j=1; j <= i/2; j++){ int tmp=dp[j]*dp[i-j]; if(max < tmp){ max=tmp; } dp[i]=max; } } max=dp[length]; return max; } public static void main(String[] args) { System.out.println(getMax(8)); } }
2. 最长递增子序列
给定一个长度为N的数组,找出一个最长的单调自增子序列(不一定连续,但是顺序不能乱)。例如:给定一个长度为6的数组A{5, 6, 7, 1, 2, 8},则其最长的单调递增子序列为{5,6,7,8},长度为4.
分析:
设长度为N的数组为{a0,a1, a2, ...an-1),则假定以aj结尾的数组序列的最长递增子序列长度为L(j),则L(j)={ max(L(i))+1, i<j且a[i]<a[j] }。也就是说,我们需要遍历在j之前的所有位置i(从0到j-1),找出满足条件a[i]<a[j]的L(i),求出max(L(i))+1即为L(j)的值。最后,我们遍历所有的L(j)(从0到N-1),找出最大值即为最大递增子序列。时间复杂度为O(N^2)。
j=0 aj=5 只有一个 L0=1
j=1 aj=6 aj-1=5 5 <6 L1=2
j=2 L2=3
j=3 1比前面的小 L3=1 重新开始
j=4 aj=2 L4=2 //1 2
j=5 aj=8 比前面大 L5=L2+1=4 // L2是最大的
代码如下:先定义dp数组并赋初值, 用于存放得到的 Lj
从1开始类似于选择排序(从0开始不需要比较 最长子序列就是1),外层遍历1-len
内层从 0 --- j-1 比如 5 2 6 3 7 4 8 aj=6的时候 需要与比较5 2比较
public class Main02 { public static int lis(int arr[], int len){ int dp[]=new int[len]; for(int i=0; i < len; i++){ dp[i]=1; } for(int j=1; j<len; j++){ for(int i=0; i < j; i++){ if(arr[j] > arr[i] && dp[j] <dp[i]+1){ dp[j]=dp[i]+1; } } } int max=0; for(int j=0; j<len; j++){ System.out.print(dp[j]+""); if(dp[j]>max){ max=dp[j]; } } System.out.println(); return max; } public static void main(String[] args) { int arr[]={5,6,7,1,2,8}; System.out.println(lis(arr,arr.length)); } }
3. 最长公共子序列
子串应该比较好理解,至于什么是子序列,这里给出一个例子:有两个母串
- cnblogs
- belong
比如序列bo, bg, lg在母串cnblogs与belong中都出现过并且出现顺序与母串保持一致,我们将其称为公共子序列。最长公共子序列(Longest Common Subsequence,LCS),顾名思义,是指在所有的子序列中最长的那一个。子串是要求更严格的一种子序列,要求在母串中连续地出现。在上述例子的中,最长公共子序列为blog(cnblogs,belong),最长公共子串为lo(cnblogs, belong)。
如何求:详细的看这一篇https://www.cnblogs.com/hapjin/p/5572483.html
还是要写下最长公共子序列的递归式才完整。
c[i,j]表示:(x1,x2....xi) 和 (y1,y2...yj) 的最长公共子序列的长度。(是长度哦,就是一个整数嘛
代码如下:
根据得到的递推式进行求解
定义存储数组时的长度要注意下 尽可能长一点0-len 长度为len+1, 直接定义为c[1000][1000]也是一样的
public class TestLcs {
public static int LCS(String str1, String str2){
int len1=str1.length();
int len2=str2.length();
int [][]c=new int[len1 + 1][len2 +1];
for(int i=0; i <= len1; i++){
for(int j=0; j <= len2; j++){
if(i==0 || j==0){
c[i][j]=0;
}else if(str1.charAt(i-1) == str2.charAt(j-1)){
c[i][j]= c[i-1][j-1] +1;
}else{
c[i][j]= Math.max(c[i-1][j], c[i][j-1]);
}
}
}
return c[len1][len2];
}
public static void main(String[] args) {
String str1 = "abcdeaafgacb";
String str2 = "acbfg";//acfg
System.out.println(LCS(str1, str2));
}
}
4. 最长公共子串(这个不会的话就比较麻烦)
(1)子序列要连续
DP求解最长公共子串
子串是一种特殊的子序列,因此同样可以用DP来解决。定义数组的存储含义对于后面推导转移方程显得尤为重要,糟糕的数组定义会导致异常繁杂的转移方程。考虑到子串的连续性,将二维数组c[i][j]用来记录具有这样特点的子串——结尾同时也为为串x1x2⋯xi与y1y2⋯yj的结尾——的长度。
得到转移方程:
最长公共子串的长度为 max(c[i,j]), i∈{1,⋯,m},j∈{1,⋯,n}。代码如下所示
public class TestCommonStr {
public static int CommonStr(String str1, String str2){
int len1=str1.length();
int len2=str2.length();
int result=0;
int c[][]=new int[len1 +1][len2 +1];
for(int i=0; i <= len1; i++){
for(int j=0; j <= len2; j++){
if(i==0 || j==0){
c[i][j]=0;
}else if(str1.charAt(i-1)== str2.charAt(j-1)){
c[i][j]=c[i-1][j-1]+1;
result= Math.max(c[i][j], result);
}else{
c[i][j]=0;
}
}
}
return result;
}
public static void main(String[] args) {
String str1 = "abcdeaafgacb";
String str2 = "acbfg";//
System.out.println(CommonStr(str1, str2));//3
}
}
5 .怎么找出这个公共子串?
直接给出代码如下
public class FIndSonStr {
public static String findSonStr(String str1, String str2){
int len1=str1.length();
int len2=str2.length();
String strTmp=str1;
int maxLen=0;
int index=0;
String str="";
int c[][]=new int[len1 +1][len2 +1];
for(int i=0; i <= len1; i++){
for(int j=0; j <= len2; j++){
if(i==0 || j==0){
c[i][j]=0;
}else if(str1.charAt(i-1)== str2.charAt(j-1)){
c[i][j]=c[i-1][j-1]+1;
}else{
c[i][j]=0;
}
if(c[i][j]>maxLen){
maxLen=c[i][j];
index=i;
}
}
}
// charAt()返回的是char char+char=int
for(int k=index-maxLen; k < index; k++){
char ch=strTmp.charAt(k);
str+=String.valueOf(ch);
}
return str;
}
public static void main(String[] args) {
String str1 = "abcdeaafgacb";
String str2 = "acbfg";//
System.out.println(findSonStr(str1, str2));//acb
}
}