上一篇
我们说到斐波那契数列的非递归解法,这篇关注另外一个会议安排的问题。
问题是:按照结束顺序输入一些数对,每个数对有自己的收益,如何安排时间可以让收益最大?
(直接从上一篇所说的视频里截的图)
如何理解这个题目?我们可以设想一个场景:
一个会议室里面要安排会议,每个会议有固定的开始结束时间以及价值。如何安排能够让这个会议室产生的价值更大? 由于原视频没有给输入输出,为了拓展性,这里规定下按顺序输入三个数组
input :
- int[] startTime (开始时间数组)
- int[] endTime(结束时间数组)
- int[] values(价值数组)
(其中,endTime按照时间增序排列,可以认为三个数组长度均大于0且都相等)
output:
- int maxValue 最大价值
图示的例子的输入:
int[] startTime = {1,3,0,4,3,5,6,8};
int[] endTime = {4,5,6,7,8,9,10,11};
int[] values = {5,1,8,4,6,3,2,4};
bestArrange(startTime,endTime,values);
思路分析:
一共八个会议,按照结束时间排序。
用OPT(i)表示前i个元素的最大值。
而第i个元素的可能值有两种:取—— OPT(i)[1],不取—— OPT(i)[0]
1.如果取:
我们必须考虑,取了第i个元素,前面有些元素是一定不取的;那么我们用表达式PRE(i)表示最大能取到的元素下标。
所以我们得到了第一个表达式
OPT(i)[1] = values[i] + OPT(PRE(i))
2.如果不取:
很简单,第i个不取,你前面的最大能取到的不就是 i-1嘛!
第二个表达式
OPT(i)[0] = OPT(i-1)
取最大值——
OPT(i) = max(values[i] + OPT(PRE(i)), OPT(i-1))
想到这里就可以动手了,直接上代码
java递归版
public int bestArrange(int[] startTime, int[] endTime, int[] values) {
int len = startTime.length;
int maxValue = 0;
// 新建一个PRE数组,长度与传入的数组相等
int[] pre = new int[len];
pre[0] = 0;
// 计算pre数组,规则是pre[i]往前递推,如果endTime[x]刚好小于startTime[i],则pre[i] = x
for (int i = 1; i < len ; i++) {
int j = i;
// 请注意!如果连endTime[0]都大于startTime[i]会发生什么呢?
// j会变成-1,所以我们固定让后面的递归式里的opt[-1]返回0
while(j >= 0 && endTime[j] > startTime[i]) {
j--;
}
pre[i] = j;
}
// startTime数组已经功成身退了,不需要再传进来了
return opt(values, endTime, pre, len - 1);
}
private int opt(int[] values, int[] endTime, int[] pre, int n) {
// 递归的两个出口,n=0时返回第一个值,n<0时返回 0
if(n == 0) {
return values[0];
}
if(n < 0) {
return 0;
}
return Math.max(opt(values, endTime, pre, n-1),
values[n] + opt(values, endTime, pre, pre[n]));
}
测试一下
public static void main(String[] args) {
int[] startTime = {1,3,0,4,3,5,6,8};
int[] endTime = {4,5,6,7,8,9,10,11};
int[] values = {5,1,8,4,6,3,2,4};
System.out.println(new MeetingArrangement().bestArrange(startTime,endTime,values));
}
结果是13,也就是取第【1,4,8】个的时候最大。
当然,还是上一篇所讲的递归算法的问题,时间复杂度高达O(2^n),n太大有爆栈的风险。
说到爆栈再多说一下,jvm中栈帧的大小一般是2M,也就2*1024*1024个字节,一个int4个字节,也就是栈里最多存50w+int类型的值,而这也不过是2^19次方的递归深度。所以递归达到二十层的时候就很危险了,大家可以尝试一下。。
那我们继续头脑风暴一下,看能不能将斐波那契数列的思路移植过来,完成2^n变n的壮举
java非递归版
思路参考斐波那契数列,斐波那契递归是n->1,非递归是1->n
(不记得的同学可以点文首的链接回去看一下哦)
那么我么也尝试着用1->n来求
public int bestArrangeNoRec(int[] startTime, int[] endTime, int[] values) {
int len = startTime.length;
int maxValue = 0;
// 新建一个PRE数组,长度与传入的数组相等
int[] pre = new int[len];
pre[0] = 0;
// 计算pre数组,规则是pre[i]往前递推,如果endTime[x]刚好小于startTime[i],则pre[i] = x
for (int i = 1; i < len ; i++) {
int j = i;
// 请注意!如果连endTime[0]都大于startTime[i]会发生什么呢?
// j会变成-1,所以我们固定让后面的递归式里的opt[-1]返回0
while(j >= 0 && endTime[j] > startTime[i]) {
j--;
}
pre[i] = j;
}
// 在此之前的代码完全可以复用,下面的改用一个数组去存放每一位的最优解
// 新建一个存放最优解的数组
int[] opt = new int[len];
// opt[0]没得选,只能是values[0]
opt[0] = values[0];
// 顺序遍历,因为后面最优解需要的数据在前面都有,一遍循环往后填就完事了
for (int t = 1;t < len;t++) {
opt[t] = Math.max(values[t] + getOpt(opt,pre[t]), getOpt(opt,t-1));
}
return opt[len-1];
}
// 特别强调下,由于之前我们是有pre(i) = -1这种情况的,
// 而数组下标没有-1,这里取巧用类似分段函数的方法返回
public int getOpt(int[] opt,int t) {
if (t>=0) {
return opt[t];
}
return 0;
}
这个方法测试后,答案也是13