动态规划算法
算法描述:
动态规划算法通常用于求解具有某种最优性质的问题。在这类问题中,可能会有许多可行解。每一个解都对应于一个值,我们希望找到具有最优值的解。基本思想也是将待求解问题分解成若干个子问题,先求解子问题,并将子问题的结果保存下来,然后从这些子问题的解得到原问题的解。
动态规划实质上是一种以空间换时间的技术,它在实现的过程中,不得不存储产生过程中的各种状态,所以它的空间复杂度要大于其它的算法。
经常会遇到复杂问题不能简单地分解成几个子问题,而会分解出一系列的子问题。简单地采用把大问题分解成子问题,并综合子问题的解导出大问题的解的方法,问题求解耗时会按问题规模呈幂级数增加。
为了节约重复求相同子问题的时间,引入一个数组,不管它们是否对最终解有用,把所有子问题的解存于该数组中,这就是动态规划法所采用的基本方法。
1.硬币找零~~
问题描述:
现存在一堆面值为 V1、V2、V3 … 个单位的硬币,问最少需要多少个硬币才能找出总值为 T 个单位的零钱?解题思路:
1,找出面值最接近T的硬币V
2,将f(T)问题的求解转换成f(T-V)+1问题的求解,以此出现递归
代码实现:
import java.util.Scanner;
public class GreedyMax {
public static void main(String[] args){
Scanner sc=new Scanner(System.in);
int n =sc.nextInt();
int a[]=new int[n];
for(int i=0;i<n;i++){
a[i]=sc.nextInt();
}
String b[]=new String[n];
for(int i=0;i<n;i++){
//将整型转换成字串
b[i]=Integer.toString(a[i]);
}
String temp="";
for(int i=0;i<n;i++){
for(int j=0;j<n-i-1;j++){
if(b[j].compareTo(b[j+1])<=0){
temp=b[j];
b[j]=b[j+1];
b[j+1]=temp;
}
}
}
String max="";
for(int i=0;i<n;i++){
max=max+b[i];
}
System.out.println(max);
}
}
2.换零钱~~
有一个数组changes,changes中所有的值都为正数且不重复。
每个值代表一种面值的货币,每种面值的货币可以使用任意张,对于一个给定值x,请设计一个高效算法,计算组成这个值的方案数。
给定一个int数组changes,代表所有零钱,同时给定它的大小n,
另外给定一个正整数x,请返回组成x的方案数,保证n小于等于100且x小于等于10000。
public class Exchange {
public int countWays(int[] changes, int n, int x) {
// write code here
//dp[i][j]表示使用changes[0~i]的钱币组成金额j的方法数
int[][] dp=new int[n][x+1];
//第一列全为1,因为组成0元就只有一种方法
for(int i=0;i<n;i++)
dp[i][0]=1;
//第一行只有changes[0]的整数倍的金额才能有1种方法
for(int j=0;j*changes[0]<=x;j++){
dp[0][j*changes[0]]=1;
}
//从位置(1,1)开始遍历
for(int i=1;i<n;i++){
for(int j=1;j<=x;j++){
//关键:使用0~i的钱币组成j-changes[i]金额的方法数+使用0~i-1钱币组成j的方法数
dp[i][j]=dp[i-1][j]+(j-changes[i]>=0?dp[i][j-changes[i]]:0);//括号括号很重要!!
}
}
return dp[n-1][x];
}
public static void main(String[] args){
int change[]={2,5,3,6};
int n=4;
int x=10;
Exchange ex=new Exchange();
System.out.println(ex.countWays(change, n, x));
}
}
3.马戏团
4.合唱团
有 n 个学生站成一排,每个学生有一个能力值,牛牛想从这 n 个学生中按照顺序选取 k 名学生,要求相邻两个学生的位置编号的差不超过 d,使得这 k 个学生的能力值的乘积最大,你能返回最大的乘积吗?
输入描述:
每个输入包含 1 个测试用例。每个测试数据的第一行包含一个整数 n (1 <= n <= 50),表示学生的个数,接下来的一行,包含 n 个整数,按顺序表示每个学生的能力值 ai(-50 <= ai <= 50)。接下来的一行包含两个整数,k 和 d (1 <= k <= 10, 1 <= d <= 50)。
输出描述:
输出一行表示最大的乘积。
输入例子:
3
7 4 7
2 50
输出例子:
49
解析:
因为有正有负,负负得正,所以要维护两个dp数组,一个存储最大,一个存储最小
定义fm[k][i]表示选中了k个学生,并以第i个学生为结尾,所产生的最大乘积
定义fn[k][i]表示选中了k个学生,并以第i个学生为结尾,所产生的最小乘积
那么fm[k+1][i+1]=max(fm[k][i]*stu[i+1],fn[k][i]*stu[i+1])
即当选中了k个学生后,再选择第i+1编号学生,所产生的最大积
但是并不能保证上一次选择的就是第i个学生,所以要遍历子数组fm[k]
另j从i到1,并且j与i+1之间小于间隔D,遍历fm[k][j],以及fn[k][j]
同理fn[k+1][i+1]=min((fm[k][i]*stu[i+1],fn[k][i]*stu[i+1])
最后,遍历一遍fm[k][i]求得最大值
5.
小东所在公司要发年终奖,而小东恰好获得了最高福利,他要在公司年会上参与一个抽奖游戏,游戏在一个6*6的棋
盘上进行,上面放着36个价值不等的礼物,每个小的棋盘上面放置着一个礼物,他需要从左上角开始游戏,每次只能
向下或者向右移动一步,到达右下角停止,一路上的格子里的礼物小东都能拿到,请设计一个算法使小东拿到价值最高的礼物。
给定一个6*6的矩阵board,其中每个元素为对应格子的礼物价值,左上角为[0,0],请返回能获得的最大价值,保证每个礼物价值大于100小于1000。
解题思路:这是一个很简单的动态规划问题
import java.util.*;
public class Bonus {
public int getMost(int[][] board) {
int n=board.length;
int[][] sum=new int[n][n];
sum[0][0]=board[0][0];//能在外面初始化的都要记得初始化
for(int i=1;i<n;i++){//原来这两个放在里面一起来着,以后这种都放在外面先单独初始化
sum[0][i]=sum[0][i-1]+board[0][i];
sum[i][0]=sum[i-1][0]+board[i][0];
}
for(int i=1;i<n;i++){
for(int j=1;j<n;j++){
sum[i][j]=Math.max(sum[i-1][j], sum[i][j-1])+board[i][j];
}
}
return sum[n-1][n-1];
}
}
6.股票交易日
在股市的交易日中,假设最多可进行两次买卖(即买和卖的次数均小于等于2),规则是必须一笔成交后进行另一笔(即买-卖-买-卖的顺序进行)。给出一天中的股票变化序列,请写一个程序计算一天可以获得的最大收益。请采用实践复杂度低的方法实现。
给定价格序列prices及它的长度n,请返回最大收益。保证长度小于等于500。
[10,22,5,75,65,80],6
返回:87
解析:
以第i天为分界线,计算第i天之前进行一次交易的最大收益profitBefore[i]
和第i天之后进行一次交易的最大收益profitAfter[i]
最后遍历一遍max{profit[i[+profitAfter[i]}
因为一共有两次交易‘分别找出第一次最大值,和第二次最大值
先从前往后遍历
因为要先买股票,找到花钱最少的
再一起找可以得到的最大收益,profitBefore[i],
用前一天的和今天新买股票进行比较
import java.util.*;
public class Stock {
public int maxProfit(int[] prices, int n) {
if(n==0)
return n;
int[] profitBefore=new int[n];
int minBuy=prices[0];
profitBefore[0]=0;
for(int i=1;i<n;i++){
minBuy=Math.min(prices[i],minBuy);
profitBefore[i]=Math.max(profitBefore[i-1],prices[i]-minBuy);
}
int[] profitAfter=new int[n];
int maxSell=prices[n-1];
profitAfter[n-1]=0;
for(int i=n-2;i>=0;i--){
maxSell=Math.max(maxSell,prices[i]);
profitAfter[i]=Math.max(profitAfter[i+1],maxSell-prices[i]);
}
int max=0;
for(int i=0;i<n;i++){
max=Math.max(max,profitBefore[i]+profitAfter[i]);
}
return max;
}
}
7.[编程题]跳石板
这条石板路要根据特殊的规则才能前进:对于小易当前所在的编号为K的 石板,小易单次只能往前跳K的一个约数(不含1和K)步,即跳到K+X(X为K的一个非1和本身的约数)的位置。 小易当前处在编号为N的石板,他想跳到编号恰好为M的石板去,小易想知道最少需要跳跃几次可以到达。
例如:
N = 4,M = 24:
4->6->8->12->18->24
于是小易最少需要跳跃5次,就可以从4号石板跳到24号石板
输入描述:
输入为一行,有两个整数N,M,以空格隔开。 (4 ≤ N ≤ 100000) (N ≤ M ≤ 100000)
输出描述:
输出小易最少需要跳跃的步数,如果不能到达输出-1
输入例子:
4 24
输出例子:
5
解析:
先写一个函数来求因数,这个方法求出的因数不包括1和他本身
import java.util.*;
public class Main{
public static void main(String[] args){
Scanner sc=new Scanner(System.in);
while(sc.hasNext()){
int n=sc.nextInt();
int m=sc.nextInt();
System.out.println(Solution(n,m));
}
}
public static int Solution(int n,int m){
if(m==n)
return 0;
int[] dp=new int[m+1];
Arrays.fill(dp,Integer.MAX_VALUE);//放满最大值
dp[n]=0;
for(int i=n;i<=m;i++){
if(dp[i]==Integer.MAX_VALUE){
dp[i]=0;
continue;
}
ArrayList<Integer> list=Getlist(i);
for(int j=0;j<list.size();j++){
int k=list.get(j);
if(i+k<=m){//在合法范围内,
dp[i+k]=Math.min(dp[i+k],dp[i]+1);//第一次到的时候是 到i的步数+1,之后再到达i+k的时候,取小的那个
}
}
}
if(dp[m]==0)
return -1;
else
return dp[m];
}
//求因数
public static ArrayList<Integer> Getlist(int n){
ArrayList<Integer> list=new ArrayList<Integer>();
for(int i=2;i*i<=n;i++){//降低时间复杂度,劈成两半,只求一边,另一边就用n/i来求
if(n%i==0){
if(i!=1&&1!=n){//这里的因数不算1和自己本身
list.add(i);
}
if(i*i!=n&&n/i!=1&&n/i!=n)//只从一半找,找到一个因数,其实就找到另一个因数
list.add(n/i);
}
}
return list;
}
}
8.题目描述:
你要出去旅游,有N元的预算住酒店,有M家酒店供你挑选,这些酒店都有价格X。
需要你正好花完这N元住酒店(不能多,也不能少)最少能住几晚?
返回最少住的天数,没有匹配的返回-1*
比如你有1000元,所有酒店都是大于1000的,则返回-1*
比如你有1000元,有1家1000元的,有1家300,有1家700。则最少能住1晚,最多住2晚(300+700)。返回1*
比如你有1000元,有1家387元,有1家2元,有一家611,则返回3(3家各住1天)*
比如你有1000元,有1家1元的,有一家2元的,有一家1001元的,则返回500(1元的1000天,2元的500天)
输入
n个int,最后一个int为你拥有的钱,[0, n-2]为酒店的价格
输出
返回最少住的天数,没有匹配的返回-1
import java.util.*;
public class Hotel {
public static void main(String[] args){
Scanner sc=new Scanner(System.in);
String str=sc.nextLine();
String[] s=str.split(" ");
int[] arr=new int[s.length-1];
int n=Integer.parseInt(s[s.length-1]);
for(int i=0;i<arr.length;i++){
arr[i]=Integer.parseInt(s[i]);
}
System.out.println(days(arr,n));
}
public static int days(int[] arr,int aim){
if(arr.length==0||aim<=0)
return -1;
int n=arr.length;
int max=Integer.MAX_VALUE;
int[][] dp=new int[n][aim+1];
for(int i=0;i<n;i++)
dp[i][0]=0;
for(int i=0;i<=aim;i++){
dp[0][i]=i%arr[0]==0?i/arr[0]:max;
}
for(int i=1;i<n;i++){
for(int j=1;j<=aim;j++){
int left=max;
if(j-arr[i]>=0&&dp[i][j-arr[i]]!=max)
left=dp[i][j-arr[i]]+1;
dp[i][j]=Math.min(dp[i-1][j], left);
}
}return dp[n-1][aim]==max?-1:dp[n-1][aim];
}
}
9.[编程题]蘑菇阵
输入描述:
第一行N,M,K(1 ≤ N,M ≤ 20, k ≤ 100),N,M为草地大小,接下来K行,每行两个整数x,y,代表(x,y)处有一个蘑菇。
输出描述:
输出一行,代表所求概率(保留到2位小数)
输入例子:
2 2 1 2 1
输出例子:
0.50
解析:
这道题不能用避开蘑菇的路径数/总路径数,因为概率是不同的
两个二维数组,一个map用来记录蘑菇在哪里,boolean类型的即可
一个double类型的dp 来记录到达这个点的概率是多少
行为i 列为j
首先如果该点有蘑菇,dp[i][j]=0,记得把dp[0][0]先刨除去
然后剩下的情况就是,原本dp[i][j]=dp[i-1][j]*概率+dp[i][j-1]*概率
但是i=0的时候,只有dp[i][j-1]*概率
j=0的时候,只有dp[i-1][j]*概率
i=n时,概率为1;j=m时,概率为1
其他时候概率为0.5
注意!!最后保留两位小数!!
String.format("%.2f",res)
import java.util.*;
public class Main {
public static void main(String[] args){
Scanner sc=new Scanner(System.in);
while(sc.hasNext()){
int n=sc.nextInt();
int m=sc.nextInt();
int k=sc.nextInt();
boolean[][] map=new boolean[n][m];
for(int i=0;i<k;i++){
int x=sc.nextInt();
int y=sc.nextInt();
map[x-1][y-1]=true;
}
double[][] dp=new double[n][m];
dp[0][0]=1;
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
if(map[i][j]){
dp[i][j]=0;
}else if(i==0&j==0){}
else{
dp[i][j]=(j==0?0:(i==n-1?dp[i][j-1]:dp[i][j-1]*0.5))+(i==0?0:(j==m-1?dp[i-1][j]:dp[i-1][j]*0.5));
}
}
}
double res=dp[n-1][m-1];
System.out.println(String.format("%.2f", res));
}
}
}