初识动态规划:如何巧妙解决“双十一”购物时的凑单问题?

------ 本文是学习算法的笔记,《数据结构与算法之美》,极客时间的课程 ------
淘宝的“双十一”购物有各种促销活动,比如“满200减50”。假设你女朋友的购物车中有 n 个(n > 100)想买的商品,她希望从里面选几个,在凑够满减的前提下,让选出来的商品价格总和和最大程度地接近满减条件(200元)。作为程序员,能不能编个代码来帮她搞定呢?解决这个问题,就要用到今天讲的动态规划(Dynamic Programming)。

动态规划学习路线

动态规划比较合适用来求解最优问题,比如求最大值、最小值等等。它可以非常显著地降低复杂度,提高代码的执行效率。不过,它也是出了名的难学。它的主要学习难点跟递归类似,那就是,求解问题的过程不太符合人类常规的思维方式。对于新手来说,想入门确实不容易。不过,等你掌握了之后,你会发现,实际上并没有想象中的那么难。

咱们分三节来讲解,分别是初识动态规划、动态规划理论、动态规划实践。

第一节,我会通过两个非常经典的动态规划问题模型,向你展示我们为什么需要动态规划,以及动态规划解题方法是如何演化出来的。实际上,你只要掌握了这两个例子的解决思路,对于其他很多动态规划问题,你都可以套用类似的思路来解决。

第二节,我会总结动态规划适合解决的问题的特征,以及动态规划思路。除此之外,我还会将贪心、回溯、动态规划这四种算法思想放在一直,对比分析它们各自的特点以及适用场景。

第三节,我会教你应用第二节讲的动态规划的理论知识,实战解决三个非常经典的动态规划问题,加深你对理论的理解。弄懂了这三节中的例子,对于动态规划这个知识点,你就算是入门了。

0-1背包问题

在讲贪心算法、回溯算法的时候,多次讲到背包问题,今天,我们依旧拿这个问题来举例。

对于一组不同质量、不可分割的物品,我们需要选择一些装入背包,在满足背包最大重量限制的前提下,背包中物品总重量的最大值是多少呢?

关于这个问题,我们上一节讲了回溯的解决方法,也就是穷举搜索所的可能的装法,然后找出满足条件的最大值。不过,回溯算法的复杂度比较高,是指数级别的。那有没有什么规律,可以有效降低时间复杂度呢?

    public int maxW = Integer.MIN_VALUE;  //  结果放到 maxW 中
    private int[] weight = {2, 2, 4, 6, 3}; // 物品重量
    private int n = 5; //物品个数
    private int w = 9; //  背包承受的最大重量
    public void f(int i, int cw) {
    	if (cw == w || i == n) { // 已装满了,或是已经考察完所有的物品
			if(cw > maxW) {
				maxW = cw;
			}
			return;
		}
    	f(i + 1, cw); // 选择不装第 i 个物品
    	if (cw + weight[i] <= w) { // 已经超过可以承受的重量的时候,就不要再装了
			f(i + 1,cw + weight[i]); // 选择装第 i 个物品
		}
    }

规律是不是不好找?那我们就举个例子、画个图看看。我们假设背包的最大承重是9。我们有5个不同的物品,每个物品的重量分别是2,2,4,6,3。如果我们把这个例子的回溯求解过程,用递归树画出来,就是下面这个样子:在这里插入图片描述

递归树中的每个节点表示一种状态,我们用(i, cw)来表示。其中,i 表示将要决策第几个物品是否装入背包,cw 表示当前背包中物品的总重量。比如,(2,2)表示我们将要决策第2个物品是否装入背包,在决策前,背包中物品的的总重量是2。

从递归树中,你应该会发现,有些子问题求解是重复的,比如图中的 f(2, 2)和f(3, 4)都被重复计算

买书问题 dp实现 题目:买书 有一书店引进了一套书,共有3卷,每卷书定价是60元,书店为了搞促销,推出一个活动,活动如下: 如果单独购买其中一卷,那么可以打9.5折。 如果同购买两卷不同的,那么可以打9折。 如果同购买三卷不同的,那么可以打8.5折。 如果小明希望购买第1卷x本,第2卷y本,第3卷z本,那么至少需要多少钱呢?(x、y、z为三个已知整数)。 1、过程为一次一次的购买,每一次购买也许只买一本(这有三种方案),或者买两本(这也有三种方案), 或者三本一起买(这有一种方案),最后直到买完所有需要的书。 2、最后一步我必然会在7种购买方案中选择一种,因此我要在7种购买方案中选择一个最佳情况。 3、子问题是,我选择了某个方案后,如何使得购买剩余的书能用最少的钱?并且这个选择不会使得剩余的书为负数 。母问题和子问题都是给定三卷书的购买量,求最少需要用的钱,所以有"子问题重叠",问题中三个购买量设置为参数, 分别为i、j、k。 4、的确符合。 5、边界是一次购买就可以买完所有的书,处理方式请读者自己考虑。 6、每次选择最多有7种方案,并且不会同实施其中多种,因此方案的选择互不影响,所以有"子问题独立"。 7、我可以用minMoney[i][j][k]来保存购买第1卷i本,第2卷j本,第3卷k本所需的最少金钱。 8、共有x * y * z个问题,每个问题面对7种选择,间为:O( x * y * z * 7) = O( x * y* z )。 9、用函数MinMoney(i,j,k)来表示购买第1卷i本,第2卷j本,第3卷k本所需的最少金钱,那么有: MinMoney(i,j,k)=min(s1,s2,s3,s4,s5,s6,s7),其中s1,s2,s3,s4,s5,s6,s7分别为对应的7种方案使用的最少金钱: s1 = 60 * 0.95 + MinMoney(i-1,j,k) s2 = 60 * 0.95 + MinMoney(i,j-1,k) s3 = 60 * 0.95 + MinMoney(i,j,k-1) s4 = (60 + 60) * 0.9 + MinMoney(i-1,j-1,k) s5 = (60 + 60) * 0.9 + MinMoney(i-1,j,k-1) s6 = (60 + 60) * 0.9 + MinMoney(i-1,j,k-1) s7 = (60 + 60 + 60) * 0.85 + MinMoney(i-1,j-1,k-1)
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值