动态规划
动态规划经常常使用于解决最优化问题,这些问题多表现为多阶段决策。
动态规划过程是:**每次决策依赖于当前状态,又随即引起状态的转移。**一个决策序列就是在变化的状态中产生出来的,所以,这种多阶段最优化决策解决问题的过程就称为动态规划。
**假设问题是由交叠的子问题所构成,我们就能够用动态规划技术来解决它。**一般来说,这种子问题出自对给定问题求解的递推关系中,这个递推关系包括了同样问题的更小子问题的解。动态规划法建议,与其对交叠子问题一次重新的求解,不如把每一个较小子问题仅仅求解一次并把结果记录在表中(动态规划也是空间换时间的)。这样就能够从表中得到原始问题的解。
动态规划解决的问题多数有重叠子问题这个特点。为降低反复计算。对每个子问题仅仅解一次,将其不同阶段的不同状态保存在一个二维数组中。
动态规划的求解步骤:
(1)划分阶段:按照问题的时间或空间特征,把问题分为若干个阶段。在划分阶段时,注意划分后的阶段一定要是有序的或者是可排序的,否则问题就无法求解。
(2)确定状态和状态变量:将问题发展到各个阶段时所处于的各种客观情况用不同的状态表示出来。当然,状态的选择要满足无后效性。
(3)确定决策并写出状态转移方程:因为决策和状态转移有着天然的联系,状态转移就是根据上一阶段的状态和决策来导出本阶段的状态。所以如果确定了决策,状态转移方程也就可写出。但事实上常常是反过来做,根据相邻两个阶段的状态之间的关系来确定决策方法和状态转移方程。
(4)寻找边界条件:给出的状态转移方程是一个递推式,需要一个递推的终止条件或边界条件。
写出状态转移方程。
例题:
1.最长公共子序列/子串
这篇讲的比较清楚:
https://blog.csdn.net/qq_25353433/article/details/98480884
给定两个字符串A和B,返回两个字符串的最长公共子序列的长度。例如,A="1A2C3D4B56”,B="B1D23CA45B6A”,”123456"或者"12C4B6"都是最长公共子序列。给定两个字符串A和B,同时给定两个串的长度n和m,请返回最长公共子序列的长度。
子序列:
/*
1.两字符串最长递增子序列 子序列不要求连续,但要保持顺序
2.
1.状态定义
dp(i , j) :以i结尾的子串和以j结尾的子串的最大子序列长度。
2.状态转移
if(s.charAt(i)==p.charAt(j)){
dp[i][j]=dp[i-1][j-1]+1;
}
else{
dp[i][j]=Math.max(dp[i-1][j],dp[i][j-1]);
}
3初始化
*/
public String findLSC(String s,String p){
int m=s.length();
int n=p.length();
//dp[i][j] 以下标为i结尾的s串和以j结尾的p串的最长公共子序列长度
int dp[][]=new int [m][n];
//存放最长公共子序列
StringBuffer sdp[][]=new StringBuffer[m][n];
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
sdp[i][j]=new StringBuffer();
}
}
//初始化dp数组 如果某一个位置和第一个元素相等,则后面都取1
for(int i=0;i<m;i++){
if(s.charAt(i)==p.charAt(0)){
while(i<m){
dp[i][0]=1;
sdp[i][0].append(p.charAt(0));
i++;
}
}
}
for(int j=0;j<n;j++){
if(s.charAt(0)==p.charAt(j)){
while(j<n){
dp[0][j]=1;
sdp[0][j].append(s.charAt(0));
j++;
}
}
}
//状态转移方程
for(int i=1;i<m;i++){
for(int j=1;j<n;j++){
if(s.charAt(i)==p.charAt(j)){
dp[i][j]=dp[i-1][j-1]+1;
sdp[i][j]=sdp[i-1][j-1].append(s.charAt(i));
}
else{
dp[i][j]=Math.max(dp[i-1][j],dp[i][j-1]);
if(sdp[i-1][j].length()>=sdp[i][j-1].length())
sdp[i][j]=sdp[i-1][j];
else sdp[i][j]=sdp[i][j-1];
}
}
}
return sdp[m-1][n-1].toString();
}
子串:
/*
最长连续子串长度 子串必须是连续的
动态规划步骤1:定义状态
状态: dp(i,j):表示A数组中以[i]结尾的子数组与B数组中以[j]结尾的子数组的最长长度。
动态规划步骤2:找到状态转移方程
如果说A[i] == B[j],那说明我们在这个两个位置上,是有公共部分的,所以我们的状态应该基于前一个状态 dp(i - 1, j - 1) + 1
那如果A[i] != B[j],说明在这个位置上没有公共部分,取0;
动态规划步骤3:初始化
*/
public int findLSC1(String s,String p) {
int m=s.length();
int n=p.length();
int[][]dp=new int[m][n];
int max=0;
//初始化
for(int i=0;i<m;i++){
if(s.charAt(i)==p.charAt(0))
dp[i][0]=1;
}
for(int j=0;j<n;j++){
if(s.charAt(0)==p.charAt(j))
dp[0][j]=1;
}
for(int i=1;i<m;i++){
for(int j=1;j<n;j++){
if(s.charAt(i)==p.charAt(j)){
dp[i][j]=dp[i-1][j-1]+1;
max=Math.max(max,dp[i][j]);
}
}
}
return max;
}
剑指offer中的动态规划题
1.连续子数组的最大和
题目:https://www.nowcoder.com/practice/459bd355da1549fa8a49e350bf3df484?tpId=13&tqId=11183&tPage=2&rp=2&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
思路:使用动态规划。
dp(i):以array[i]为末尾元素的子数组的和的最大值,子数组的元素的相对位置不变
dp(i)=max(dp(i-1)+array[i],array[i]) 状态转移方程
max:所有子数组的和的最大值
max=max(max,dp(i))
参考:https://mp.csdn.net/mdeditor/88391768#
/*
动态规划步骤:
1.定义状态 int dp[]=new int[array.length]
dp(i):以array[i]为末尾元素的连续子数组的和的最大值
2.定义状态转移方程 dp(i)=max(dp(i-1)+array[i],array[i])
3.初始化状态
*/
public int FindGreatestSumOfSubArray(int[] array) {
if(array.length==0) return 0;
//1
int dp[]=new int[array.length];
//2
for(int i=0;i<array.length;i++){
dp[i]=array[i];
}
int max=dp[0];
//3
for(int i=1;i<array.length;i++){
dp[i]=Math.max(dp[i-1]+array[i],dp[i]);
if(dp[i]>max) max=dp[i];
}
return max;
}
2.求第N个丑数
题目:https://www.nowcoder.com/practice/6aa9e04fc3794f68acf8778237ba065b?tpId=13&tqId=11186&tPage=2&rp=2&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
解析:
丑数
- 把只包含素因子2、3和5的数称作丑数(Ugly Number)。
- 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。
- 丑数规律是前面的数*2||*3||*5都会得到一个丑数,为了避免重复 我们就想了一个办法,取当前最小的丑数然后加入丑数数组。
*/
//要注意,后面的丑数是有前一个丑数乘以2,3,5中的一个得来。因此可以用动态规划去解
//状态转移方程nums[i]=Math.min(nums[multiply2]*2,Math.min(nums[multiply3]*3,nums[multiply5]*5));
public int GetUglyNumber_Solution(int index) {
/*
1.状态 int [] dp=new int [index] dp[i] 表示第i+1个丑数
2.状态转移方程
3.初始化
*/
if(index<=0)
return 0;
//1
int [] dp=new int [index]; //dp[i] 第i+1个丑数
//2
for(int i=0;i<index;i++){
dp[i]=1;
}
int index2=0;
int index3=0;
int index5=0;
//3
for(int i=1;i<index;i++){
int min=Math.min(dp[index2]*2,Math.min(dp[index3]*3,dp[index5]*5));
dp[i]=min;
if(min==dp[index2]*2) index2++;
if(min==dp[index3]*3) index3++;
if(min==dp[index5]*5) index5++;
}
return dp[index-1];
}
题解:
https://www.nowcoder.com/questionTerminal/6aa9e04fc3794f68acf8778237ba065b