动态规划
杭电oj做到了最大子序列和,自己的换了好多方法都超时了,网上一查需要用到动态规划,趁此机会学习一下动态规划
但是感觉动态规划有点像这个递归哈哈哈哈
1.动态规划,就是利用历史记录来避免计算的重复,而这些历史记录我们需要一些变量来保存,一般用到一维数组还有二维数组来保存
2.三个步骤
(1) 定义数组元素的含义,例如你的dp【i】代表的什么含义
(2)找出数组元素之间的关系式,有点类似高中所学的数学归纳法,都是通过已知元素来推未知元素
(3)找出初始值,学过数学归纳法的都知道,虽然我们知道了数组元素之间的关系式,例如 dp[n] = dp[n-1] + dp[n-2],我们可以通过 dp[n-1] 和 dp[n-2] 来计算 dp[n],
但是,我们得知道初始值啊,例如一直推下去的话,会由 dp[3] = dp[2] + dp[1]。而 dp[2] 和 dp[1] 是不能再分解的了,,
所以我们必须要能够直接获得 dp[2] 和 dp[1] 的值而这,就是所谓的初始值。由了初始值,并且有了数组元素之间的关系式
,那么我们就可以得到 dp[n] 的值了,而 dp[n] 的含义是由你来定义的,你想求什么,就定义它是什么,这样,这道题也就解出来了。
3.
小青蛙跳台阶
问题描述,一只青蛙一次可以跳上一级台阶,也可以跳上二级台阶。求台阶跳上一个n级台阶总共有多少种方法。
三大步骤
一定义dp的含义,我们的问题是跳上n级台阶时的跳法,我们就定义dp【i】的含义是跳上一个n级台阶总共有dp【i】种跳法。
二.找出元素之间的关系式,因为在每一阶台阶时都是从n-1阶还有n-2阶台阶跳上来的
由于我们要算所有可能的跳法,所以有dp【i】=dp【i-1】+dp【i-2】;
三.找出初始条件
当 n = 1 时,dp[1] = dp[0] + dp[-1],而我们是数组是不允许下标为负数的,所以对于 dp[1],
我们必须要直接给出它的数值,相当于初始值,显然,dp[1] = 1。一样,dp[0] = 0.(因为 0 个台阶,那肯定是 0 种跳法了)。于是得出初始值:
1. dp[0] = 0. dp[1] = 1. | dp[0] = 0. | |-dp[1] = 1.-| | n <= 1
时,dp[n] = n. |
代码实现:
int[] dp = new int[n+1];
// 给出初始值
dp[0] = 0;
dp[1] = 1;
// 通过关系式来计算出 dp[n]
for(int i = 2; i <= n; i++){
dp[i] = dp[i-1] + dp[i-2];
}
System.out.println(dp[i]);
(2)
例题二,找零钱
已知不同面值的钞票,求如 何用最少数量的钞票组成某个金额,求可 以使用的最少钞票数量。如果任意数量的已知面值钞票都无法组成该金额, 则返回-1。
输入coins 1 3 5 money 11 输出 3
第一步,确认dp【i】的含义(i元钱时的最少硬币方案)
第二步,找出元素之间的关系式
第三步,找出初始值
dp【0】={0}=0 |
dp【1】={dp【1-arr【1】】+1}=1 |
dp【2】={dp【2-arr【1】】+1}=2 |
dp【3】={dp【3-arr【2】】+1}=1 |
dp【4】={dp【4-arr【2】】+1}=2 |
...... |
dp【i】={dp【i-arr【j】(arr[j]<i并且是小于i中最大的面值)】+1}= |
代码实现:
int money = sc.nextInt();
int[] arr = { 1, 3, 5 };
int[] dp = new int[money + 1];// 每一个都代表一种钱数的拼凑最小值方案
int max = 0;
dp[0] = 0;
for (int i = 1; i < dp.length; i++) {
for (int j = 0; j < arr.length; j++) {
if (arr[j] >= i) {//找到这个临界值,也就是小于i并且是最大的那个面值
if (arr[j] == i) {//如果等于i,证明就是一个硬币就可以
max = j;//通过索引实现这个arr【j】
} else {
max = j - 1;
}
break;//一旦找出这个临界值,立即跳出
}
}
if (max == -1) {
dp[i] = 1;
} else {
dp[i] = dp[i - arr[max]] + 1;
}
}
System.out.println(dp[dp.length - 1]);
}
}
}
(3)
例题三,找出最短时间(杭电1260)
题目描述:
题意:有K个人买票,可以每个人买单票,给出K个单买时间;也可以和前一个人买双票,给出K-1个买双票时间
第一步,设定dp的含义,因为题目中说是求k个人买票最短时间,所以我们就设dp[k]为k个人买票花费的最短时间
第二步,找出元素之间的关系,在这里我举一个例子
输入:
单人买票时间 arr[i] | 25 | 20 | 24 | 21 |
---|---|---|---|---|
相邻双人买票时间 d[i] | 40 | 25 | 42 |
dp[0]=0 |
dp[1]=25 |
dp[2]=min(25+20,40) |
dp[3]=min(min(25+20,40)+24,42+25) |
..... |
dp[i]=min(dp[i-1]+arr[i],d[i-1]+dp[i-2]) |
第三步,找出初始值
dp【0】=0;
dp【1】=arr【1】;
第四步,代码实现:
import java.util.*;
public class Main3 {
// alphabetic order.字母序
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
while (n-- > 0) {
int k = sc.nextInt();
int[] arr = new int[k + 1];//单人买票时间
int d[] = new int[k];
for (int i = 1; i < arr.length; i++) {
arr[i] = sc.nextInt();
}
if (k == 1) {//k=1的时候直接就输出
if(arr[1]<10) {
System.out.println("08:00:0"+arr[1]+" am");
}else {
System.out.println("08:00:"+arr[1]+" am");
}
} else {
for (int i = 1; i < d.length; i++) {k!=1的时候就有d【】,输入d【】的值
d[i] = sc.nextInt();
}
int[] dp = new int[k + 1];
dp[0] = 0;//初始值
dp[1] = arr[1];//初始值
for (int i = 2; i < dp.length; i++) {
dp[i] = Math.min(dp[i - 1] + arr[i], d[i - 1] + dp[i - 2]);//主要的公式
}
int hour=dp[k]/3600;
int min=dp[k]/60%60;
int s=dp[k]%60;
int sumhour = hour + 8;
if (sumhour < 10) {
System.out.print("0" + sumhour + ":");
} else {
System.out.print(sumhour + ":");
}
if (min < 10) {
System.out.print("0" + min + ":");
} else {
System.out.print(min + ":");
}
if (s < 10) {
System.out.print("0" + s + " ");
} else {
System.out.