动态规划学习
Posted zero-ng
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了动态规划学习相关的知识,希望对你有一定的参考价值。
1.斐波那契数
- 阶段划分:按照数字的位置划分为0~n
- 状态和状态变量:使用一维数组result,result[i]代表对应的斐波那契数
- 状态转移方程:result[i] = result[i-1] +result[i-2]
- 边界条件:位置为0和1上的是肯定的,不用计算的
public static int solutionFibonacci(int n){
if(n==0){
return 0;
}else if(n == 1){
return 1;
}else {
int result[] = new int[n+1];
result[0] = 0;
result[1] = 1;
for(int i=2;i<=n ; i++){
result[i] = result[i-1] +result[i-2];
}
return result[n] ;
}
}
2.数组最大不连续递增子序列
- 阶段划分:按照数组的元素个数递增划分为0~n
- 状态和状态变量:使用一维数组temp,temp[n]代表第n个元素为止最大不连续递增子序列个数
- 状态转移方程:temp[i] = temp[j]+1
- 边界条件:temp[i]为它前面的比它小的数中对应的temp最大值+1,所以边界为a[i]>a[j]
public static int MaxChildArrayOrder(int a[]) {
int n = a.length;
int temp[] = new int[n];//temp[i]代表0...i上最长递增子序列
for(int i=0;i<n;i++){
temp[i] = 1;//初始值都为1
}
for(int i=1;i<n;i++){
for(int j=0;j<i;j++){
if(a[i]>a[j]&&temp[j]+1>temp[i]){
//如果有a[i]比它前面所有的数都大,则temp[i]为它前面的比它小的数的那一个temp+1取得的最大值
temp[i] = temp[j]+1;
}
}
}
int max = temp[0];
//从temp数组里取出最大的值
for(int i=1;i<n;i++){
if(temp[i]>max){
max = temp[i];
}
}
return max;
}
3.数组最大连续子序列和
- 阶段划分:按照数字的位置划分为0~n
- 状态和状态变量:a[i]表示元素,sum表示当前元素为止所有元素和
- 状态转移方程:sum = Math.max(sum+a[i], a[i])
- 边界条件:a[i]>sum+a[i],当前元素大于前面的所有元素和,就抛弃前面的所有元素
public static int MaxContinueArraySum(int a[]) {
int n = a.length;
int max = a[0];
int sum = a[0];
for(int i=1;i<n;i++){
sum = Math.max(sum+a[i], a[i]);
if(sum>=max){
max = sum;
}
}
return max;
}
4.数字塔从上到下所有路径中和最大的路径
- 阶段划分:按照层数划分
- 状态和状态变量:dp[i][j]代表到达第i层第j个数字的最大路径和
- 状态转移方程:dp[i][j] = Math.max(dp[i-1][j-1], dp[i-1][j]) + n[i][j];
- 边界条件:第0列只有一条路,所以要分开考虑,不然会发生越界,可以考虑空出第0行,就不用if了
public static int minNumberInRotateArray(int n[][]) {
int max = 0;
int dp[][] = new int[n.length][n.length];
dp[0][0] = n[0][0];
for(int i=1;i<n.length;i++){
for(int j=0;j<=i;j++){
if(j==0){
//如果是第一列,直接跟他上面数字相加
dp[i][j] = dp[i-1][j] + n[i][j];
}else{
//如果不是第一列,比较他上面跟上面左面数字谁大,谁大就跟谁相加,放到这个位置
dp[i][j] = Math.max(dp[i-1][j-1], dp[i-1][j]) + n[i][j];
}
max = Math.max(dp[i][j], max);
}
}
return max;
}
5.两个字符串最大公共子序列
- 阶段划分:按照字符串的序列划分
- 状态和状态变量:dp[i][j]代表A字符串前i行和B字符串第j行最大公共子序列长度
- 状态转移方程:dp[i][j] = Math.max(dp[i][j-1],dp[i-1][j]);
- 边界条件:如果a[i]=b[j],dp[i][j] = dp[i-1][j-1]+1,否则就比较字符串分别减少一个后的最大公共子序列的值
public class MaxTwoArraySameOrder {
public static int MaxTwoArraySameOrderMethod(String str1,String str2) {
int m = str1.length ( );
int n = str2.length ( );
int dp[][] = new int[m+1][n+1];
for(int i=0 ; i<=m; i++){
dp[i][0] = 0;
}
for(int i=0 ; i<=n ; i++){
dp [0][i] = 0;
}
for(int i=1; i<=m ;i++){
for(int j=1; j<=n ; j++){
if( str1.charAt(i-1) == str2.charAt(j-1)){
dp[i][j] = dp[i-1][j-1]+1;
}
else{
dp[i][j] = Math.max(dp[i][j-1],dp[i-1][j]);
}
}
}
for(int i=1; i<=m ;i++){
for(int j=1; j<=n ; j++)
System.out.print(dp[i][j]+" ");
System.out.println("row "+i);
}
return dp[m] [n];
}
public static void main (String[] args) {
String str1 = "BDCABA";
String str2 = "ABCBDAB" ;
int array=MaxTwoArraySameOrderMethod ( str1,str2);
System.out.println ("max LCS="+array ) ;
}
}
6.背包问题
- 阶段划分:按照物品序列划分
- 状态和状态变量:dp[i][j]代表最大值,i代表从物品只考虑物品1~i,j代表背包容量
- 状态转移方程:dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-w[i]]+p[i]);
- 边界条件:j>=w[i]背包容量是否大于物品体积
public static int PackageHelper(int n,int w[],int p[],int v) {
//设置一个二维数组,横坐标代表从第一个物品开始放到第几个物品,纵坐标代表背包还有多少容量,dp代表最大价值
int dp[][] = new int[n+1][v+1];
for(int i=1;i<n+1;i++){
for(int j=1;j<=v;j++){
if(j>=w[i]){
/*
* 当能放得下这个物品时,放下这个物品,价值增加,但是空间减小,最大价值是dp[i-1][j-w[i]]+p[i]
* 当不放这个物品时,空间大,物品还是到i-1,最大价值是dp[i-1][j]
* 比较这两个大小,取最大的,就是dp[i][j]
*/
dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-w[i]]+p[i]);
}else{
//如果放不下,就是放上一个物品时的dp
dp[i][j] = dp[i-1][j];
}
}
}
return dp[n][v];
}
public static int PackageHelper2(int n,int w[],int p[],int v) {
//设置一个二维数组,横坐标代表从第一个物品开始放到第几个物品,纵坐标代表背包还有多少容量,dp代表最大价值
int dp[] = new int[v+1];
for(int i=1;i<=n;i++){
for(int j=v;j>0;j--){
if(j>w[i]){
dp[j] = Math.max(dp[j], dp[j-w[i]]+p[i]);
}else{
dp[j] = dp[j];
}
}
}
return dp[v];
}
7.找零钱问题
这里是种类问题,如果是找零个数最少,也是同样的,修改dp代表的东西就可以
- 阶段划分:按照使用的硬币种类个数分
- 状态和状态变量:dp[i][j]代表方法个数,i代表0~i种硬币,j代表需要找的零钱总数
- 状态转移方程:dp[i][j] = dp[i-1][j] + dp[i][j-num[i]];
- 边界条件:j>num[i]
public static int SmallMoney(int num[],int target) {
int m = num.length;
int dp[][] = new int[m][target+1];
dp[0][0] = 1;
for(int i=1;i<=target;i++){
if(i%num[0] == 0){
dp[0][i] = 1;//第一行数值填写
}else{
dp[0][i] = 0;
}
}
for(int i=0;i<m;i++){
dp[i][0] = 1;//第一列数值填写
}
for(int i=1;i<m;i++){
for(int j=1;j<=target;j++){
if(j<num[i]){
dp[i][j] = dp[i-1][j];
}else{
dp[i][j] = dp[i-1][j] + dp[i][j-num[i]];
}
}
}
return dp[m-1][target];
}
public static int SmallMoney2(int num[],int target) {//使用一维数组滚动
int m = num.length;
int dp[] = new int[target+1];
dp[0] = 1;
for(int i=1;i<=target;i++){
if(i%num[0] == 0){
dp[i] = 1;
}else{
dp[i] = 0;
}
}
for(int i=1;i<m;i++){
for(int j=1;j<=target;j++){
if(j>=num[i]){
dp[j] = dp[j] + dp[j-num[i]];
}
}
}
return dp[target];
}
总结:动态规划主要的思想就是用空间换取时间。把会重复进行计算的小问题的答案存储下来,然后一步步得出大问题的答案,
把问题按照阶段划分就是把问题分成小问题,状态转移方程就是小问题答案组成大问题答案,边界值就是根据条件选择不同的状态转移方程。
虽然思路是这样,但是如何进行划分,如何考虑条件也是评估我们水平的一个因素。不同的思路得到的方法也是不一样的,归根结底还是思路
更重要,动态规划只是一种方法,代表一种思路,想要熟练,还得先多见识一下,总结规律。
参考链接:https://blog.csdn.net/zw6161080123/article/details/80639932
https://www.jianshu.com/p/6e3dcc476c6a
以上是关于动态规划学习的主要内容,如果未能解决你的问题,请参考以下文章