【基础决定高度】【算法】从零开始手撕动态规划(DynamicProgramming)(二)

上一篇
我们说到斐波那契数列的非递归解法,这篇关注另外一个会议安排的问题。

问题是:按照结束顺序输入一些数对,每个数对有自己的收益,如何安排时间可以让收益最大?

在这里插入图片描述
(直接从上一篇所说的视频里截的图)

如何理解这个题目?我们可以设想一个场景:
一个会议室里面要安排会议,每个会议有固定的开始结束时间以及价值。如何安排能够让这个会议室产生的价值更大? 由于原视频没有给输入输出,为了拓展性,这里规定下按顺序输入三个数组
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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值