动态规划是笔试面试经常考到的问题,之前一篇博客介绍过如何利用动态规划求最长公共子串和最优二叉查找树问题(https://blog.csdn.net/m0_37568814/article/details/82713509)。这篇博客着重介绍动态规划其他方面的应用。相关内容详见https://blog.csdn.net/u013309870/article/details/75193592
本文在此博客的基础上给出了小朋友过桥问题的Java代码实现。
1.小朋友过桥问题:每次只允许不大于两人通过,他们只有一个手电筒,所以每次过桥的两个人需要把手电筒带回来,i号小朋友过桥的时间为timeCost[i],两个人过桥的总时间为二者中时间长者。问所有小朋友过桥的总时间最短是多少?
若四个人过桥花费的时间分别为 1 2 5 10,贪心算法计算得19(即每次让跑的最快的那个送手电筒过来---难道这就是能者多劳么),实际答案应该则是17。
具体步骤如下:
第一步:1和2过去,花费时间2,然后1回来(花费时间1)---2+1=3;
第二歩:3和4过去,花费时间10,然后2回来(花费时间2)---10+2=12;
第三部:1和2过去,花费时间2,总耗时17---12+3+2=17。
先将所有人按花费时间递增进行排序,排序后数组假设为a,假设前i个人过河花费的最少时间为opt[i]。
1)考虑前i-1个人过河的情况,即河这边还有1个人,河那边有i-1个人,并且这时候手电筒肯定在对岸,则:让花费时间最少的人(a[1])把手电筒送过来,然后和第i个人一起过河,所以, opt[i] = opt[i-1] + a[1] + a[i]
2)如果河这边有两个人,一个是第i号,另外一个编号任意(因为a[i]是花费时间最长的那个人,所以其他任何人都不影响结果),河那边有i-2个人,并且手电筒肯定在对岸,让花费时间最少的人把电筒送过来(a[1]),然后第i个人和另外一个人一起过河(花费时间a[i]),由于花费时间最少的人在这边,所以下一次送手电筒过来的一定是花费次少(a[2])的,送过来后花费最少的和花费次少的一起过河,花费时间为a[2].所以,opt[i] = opt[i-2] + a[1] + a[i] + 2*a[2]
结合以上两种情况: opt[i] = min{opt[i-1] + a[1] + a[i] , opt[i-2] + a[1] + a[i] + 2*a[2] } (其中i>2,opt[1] = a[1],opt[2]=a[2])
package ExamTest;
import java.util.*;
/**
* Created by ZhangAnmy on 18/10/3.
*/
public class DPTest03 {
public static void main(String[] args)
{
// int[] timeCost = {0,1,2,5,10};//下标分别对应小朋友的标号,下标0对应的是0即为没有此小朋友.
Scanner sc = new Scanner(System.in);
int numOfChild = sc.nextInt();
int[] timeCost = new int[numOfChild+1];
timeCost[0] = 0;//下标分别对应小朋友的标号,下标0对应的是0即为没有此小朋友.
for(int i=1;i<numOfChild+1;i++)
{
timeCost[i] = sc.nextInt();
}
Arrays.sort(timeCost);
int[] optCost = new int[timeCost.length];
optCost[0] = 0;
optCost[1] = timeCost[1];
optCost[2] = timeCost[2];
for(int i=3;i<timeCost.length;i++)
{
optCost[i] = Math.min(optCost[i-1]+timeCost[1]+timeCost[i],optCost[i-2]+timeCost[1]+2*timeCost[2]+timeCost[i]);
}
}
}
运算结果:
Input:
4---小朋友个数
1 5 10 2 ---每个小朋友过桥花费的时间
Output:
17
2. 钢条切割问题:给定一段长度为n的钢条和一段价格表pi,求切割方案是的收益rn最大,注意最优方案也可能不需要切割。
价格与切割长度的对应关系如下:
package ExamTest;
import java.util.HashMap;
import java.util.Map;
/**
* Created by ZhangAnmy on 18/10/3.
* 钢条切割求最大效益问题.不同的长度价格不同,问给定一定长度的钢条,怎么切割使得获取的利益最大.
*/
public class DPTest02 {
public static void main(String[] args)
{
Map<Integer,Integer> map = new HashMap<Integer, Integer>();
// map.put(0,0);
map.put(1,1);
map.put(2,5);
map.put(3,8);
map.put(4,9);
map.put(5,10);
map.put(6,17);
map.put(7,17);
map.put(8,20);
map.put(9,24);
map.put(10,30);
int n=4;
System.out.println(getMaxProfit(map,n));
System.out.println(getMaxProfitMemo(map));
int[] allMaxProfile = getMaxProfit_2(map);
for(int i=1;i<allMaxProfile.length;i++)
{
System.out.println(i+"-->"+allMaxProfile[i]);
}
}
//递归法
public static int getMaxProfit(Map<Integer,Integer> map,int n)
{
if(n==0)
return 0;
int profit = 0;
for(int i=1;i<=n;i++)
{
profit = Math.max(profit,map.get(i)+getMaxProfit(map,n-i));
}
return profit;
}
//备忘录版本
public static int getMaxProfitMemo(Map<Integer,Integer> map)
{
int[] memo=new int[map.size()];
for(int i=0;i<memo.length;i++)
{
memo[i]=-1;
}
return getMaxProfit1(map,6,memo);
}
public static int getMaxProfit1(Map<Integer,Integer> map,int n,int[] memo)
{
if(n==0)
return 0;
if(memo[n]>0)
{
return memo[n];
}
int profit = 0;
for(int i=1;i<=n;i++)
{
profit = Math.max(profit,map.get(i)+getMaxProfit1(map,n-i,memo));
}
memo[n] = profit;
return memo[n];
}
//自底向上的动态规划
public static int[] getMaxProfit_2(Map<Integer,Integer> map)
{
int []r=new int[map.size()+1];
for(int i=1;i<=map.size();i++)
{
int bestProfit=-1;
for(int j=1;j<=i;j++) //内循环求得r[i]的最优解
bestProfit=Math.max(bestProfit, map.get(j)+r[i-j]);
r[i]=bestProfit;//外循环保存每个最优解
}
return r;
}
}
其中,自底向上的动态规划的方法返回的所有长度对应的最大收益如下:(第一个数为钢条长度,第二个数为最大收益)
1-->1
2-->5
3-->8
4-->10
5-->13
6-->17
7-->18
8-->22
9-->25
10-->30
其他动态规划应用问题(区间模型,背包模型),后续再归纳完善,今天先到这儿吧^_^^_^