关闭

刷刷笔试题~~~[动态规划!!!!]

标签: 动态规划
1005人阅读 评论(0) 收藏 举报
分类:

动态规划算法

算法描述:

动态规划算法通常用于求解具有某种最优性质的问题。在这类问题中,可能会有许多可行解。每一个解都对应于一个值,我们希望找到具有最优值的解。基本思想也是将待求解问题分解成若干个子问题,先求解子问题,并将子问题的结果保存下来,然后从这些子问题的解得到原问题的解。

动态规划实质上是一种以空间换时间的技术,它在实现的过程中,不得不存储产生过程中的各种状态,所以它的空间复杂度要大于其它的算法


经常会遇到复杂问题不能简单地分解成几个子问题,而会分解出一系列的子问题。简单地采用把大问题分解成子问题,并综合子问题的解导出大问题的解的方法,问题求解耗时会按问题规模呈幂级数增加。

为了节约重复求相同子问题的时间,引入一个数组,不管它们是否对最终解有用,把所有子问题的解存于该数组中,这就是动态规划法所采用的基本方法。


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.[编程题]跳石板

小易来到了一条石板路前,每块石板上从1挨着编号为:1、2、3.......
这条石板路要根据特殊的规则才能前进:对于小易当前所在的编号为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.[编程题]蘑菇阵

现在有两个好友A和B,住在一片长有蘑菇的由n*m个方格组成的草地,A在(1,1),B在(n,m)。现在A想要拜访B,由于她只想去B的家,所以每次她只会走(i,j+1)或(i+1,j)这样的路线,在草地上有k个蘑菇种在格子里(多个蘑菇可能在同一方格),问:A如果每一步随机选择的话(若她在边界上,则只有一种选择),那么她不碰到蘑菇走到B的家的概率是多少?

输入描述:
第一行N,M,K(1 ≤ N,M ≤ 20, k ≤ 100),N,M为草地大小,接下来K行,每行两个整数x,y,代表(x,y)处有一个蘑菇。
输出描述:
输出一行,代表所求概率(保留到2位小数)
输入例子:
2 2 1
2 1
输出例子:
0.50

解析:

这道题不能用避开蘑菇的路径数/总路径数,因为概率是不同的

走不同路径的概率是不相等的。 
如   :
1 2 3
4 5 6
1->2 概率是0.5,2->3概率是0.5,3->6概率是1
1->2 概率是0.5,2->5概率是0.5,5->6概率是1
1->4 概率是0.5,4->5概率是   1,3->6概率是1
可以发现1-2-3-6与1-2-5-6的概率为0.25,而1-4-5-6概率为0.5
所以直接用可达路径数/总路径数求概率是不对的。

两个二维数组,一个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));
        }

	}
	
}






0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:43261次
    • 积分:1453
    • 等级:
    • 排名:千里之外
    • 原创:98篇
    • 转载:52篇
    • 译文:0篇
    • 评论:12条
    最新评论