动态规划
以此谨记自己学习java心得
这几天为了笔试一直在牛客网刷题,分享一题动态规划的题目,想了好几天,今天开了几篇文章就开窍了。
首先我们来说说什么是动态规划算法。这里引用一下我参考的文章,讲的挺详细的
这是我参考的CSDN文章
看完上面这篇,我来简单介绍一下步骤吧。
1.一般算法题都是用二维数组来求解的,我这里比方说dp[][]
那这个数组dp[i][j]到底是什么含义,是弄清动态规划算法的前提,一般性的,我们都是题目问我们什么问题,我们就把这个意思转义给数组dp[i][j]。
2.动态规划的dp[i][j],在这个位置上,dp[i][j]是肯定和其他方位的位置有关系的,可能是和左侧有关,也可能是和上侧有关,也可能和左上侧有关,都是要看题目意思来确定的。
3.动态规划就是从小到大去求解,先确定某一个位置的最优解,比方说
array[i][j]=Math.min(array[i][j],array[i][k]+array[k+1][j]);
这里面的Math.min就是用来确定最小值的,在这里我们要考虑到数组越界的问题,那么必须初始化一些值,用来防止越界,以及循环后数组各个地方都有对应的值。
下面引入一道美团校招的编程题
有 N 堆金币排成一排,第 i 堆中有 C[i] 块金币。每次合并都会将相邻的两堆金币合并为一堆,成本为这两堆金币块数之和。经过N-1次合并,最终将所有金币合并为一堆。请找出将金币合并为一堆的最低成本。
其中,1 <= N <= 30,1 <= C[i] <= 100
第一行输入一个数字 N 表示有 N 堆金币
第二行输入 N 个数字表示每堆金币的数量 C[i]
输出一个数字 S 表示最小的合并成一堆的成本
输入例子1:
4
3 2 4 1
输出例子1:
20
输入例子2:
30
10 20 30 40 50 60 70 80 90 100 99 89 79 69 59 49 39 29 19 9 2 12 22 32 42 52 62 72 82 92
输出例子2:
7307
拿到题目,看到相邻两字,就考虑动态规划问题。
这里第一步确定dp[i][j]是什么意思,
我这里规定dp[i][j]就是从第i个位置开始到第j个位置结束,这一堆合并金币所需的最小成本。
第二步 确定各个点的关系式
打个比方说如果输入4个数 2 3 6 1(为了方便理解,我把下标定为从1开始 到4结束)
那我们就先比方说dp[1,3]也就是从第一个位置到第三个位置结束,那也就是2 3 6的最小成本数,我们有两种情况,
从2到3 是一堆 然后6是一堆; 2是一堆,3 到6是一堆。
那么我们这个dp[1][3]的公式就是dp[1][2]+dp[3][3]+2+3+6或者dp[1,1]+dp[2][3]+2+3+6;后面的2+3+6的意思是最后一次的成本数,也就是不管你是从哪个开始到哪个结束,最后一次的成本永远是从开始到结束的总和。比如说如果是从2 3为一堆先开始,那就是2 3 6=>5 6(5)=>11(5+11)
括号里是成本数。
那我们现在可以确定dp[i][j]的公式就是设置一个变量k
i<=k<j,dp[i][j]=dp[i][j] = Math.min(dp[i][j],dp[i][k] + dp[k + 1][j] + lastMoney); 这个k是放在循环里的。
第三步看看数组是否越界,发现在这个情况下,数组没有越界现象。
下面张贴代码,代码也写了注释
package Test0410;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Scanner;
public class MoneyHeadNew {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n=scanner.nextInt();
// int m=scanner.nextInt();
// ArrayList<Integer> list = new ArrayList<>();
int array[][]=new int[n+1][n+1];
int sum[]=new int[n+1];
for (int i = 1; i < n+1; i++) {
int a=scanner.nextInt();
sum[i]=sum[i-1]+a;
}
// System.out.println(Arrays.toString(sum));
for (int len = 2; len <=n ; len++) {//一堆有多少组成
//比如len=2 就是组合二堆为1堆
//len=3 就是组合三堆为1堆
for (int i = 1; i <=n-len+1 ; i++) {//起点
int j=i+len-1;//终点
array[i][j]=Integer.MAX_VALUE;//为了一开始得到最小值
int leastSum=sum[j]-sum[i-1];
for (int k=i;k<j;k++) {
//转移方程就是因为合成一堆有不同的组合方法
//如2 3 6 1
//当len=2时 ,组合有2 3 3 6 6 1
//获取当前组合的最小值
//当len=3
//组合有 一堆是2 3 6 组合为2 3 6 组合为2 3 6
//比如组合2 3 6 就要获取2 3的最优解 3 6的最优解
//当Len=4
//组合有2 3 6 1 2 3 6 1 2 3 6 1
//因为之前已经得到了2 3 6 的最优解 和 3 6 1的最优解
//继续比较两者到底谁小
//这句话是真的核心
//从因为k刚开始是从i开始
//所以一开始也就是从i 开始到i结束所需成本+从i+1开始到j的成本
//然后循环后是从i到i+1结束的成本+从i+1+1到j的成本
//总的来说也就是从i到j 它要分成j-i个部分来相加求和找最小
//从前一段到后一段,从前一段到后一段
//前一段从0到最后
//后一段从最后到0
//sum 就是此次成本
//因为不管是从i到j哪一次
//最后的相加的成本一定是从i到j所有数字之和
//比方说2 3 6 最后一次肯定是2+3+6
//所以最后一定要加上11 不管是从2 3 还是 3 6 先合成一堆
array[i][j]=Math.min(array[i][j],array[i][k]+array[k+1][j]+leastSum);
}
}
}
System.out.println(array[1][n]);
}
}
想了好几天的问题,今天终于想明白了。加油,多看几篇文章,写几题代码,你也能写出来的!