【算法设计与分析】动态规划第一次作业

上一篇讲述了我们改如何去做动态规划的题,现在就让我们将按照上一篇的步骤,拿算法与设计课的作业来练练手吧。

一、问题描述

有一个专业的推销员,计划在一条街道上门推销商品。每栋房子都会购买一定数量的商品,但是如果本栋房子主人购买商品后,他的邻居就不会购买该商品。如果推销员向两个相邻的邻居同时推销该商品,则违反推销规则,推销失败。

输入:给定一个代表住户购买商品的非负整数列表
输出:确定不触发推销失败规则的情况下,输出推销员可以推销出的最大商品数量。

示例:
Input: [2,2,5,1]
Output: 7

二、答案要求

  • 要求采用动态规划方法解决该问题
  • 要求给出递推方程,并给出分析过程
  • 学生自己提供示例,并给出对应的推销备忘录(保存中间最优结果)与推销标记表格

三、思路分析

在本题中,我们按顺序将住户购买的商品数量存放在一个数组arr[ ] 中方便我们使用。

(1)定义数组及其含义

定义一个数组dp[ ] 用来存放递推所需的数据,在其中dp[0] 代表的时推销到第1户住户时,卖出去的最大商品数,dp[i]的含义就是:推销到第i+1户住户时能卖出去的最大商品数。

(2)找出数组元素之间的关系式(递推方程)

第i户住户是否购买商品取决于上一家住户是否已经购买了,所以这有两种情况:

  • A:上一家( i - 1 )住户已经购买了商品,所以第i户住户就不能再买了,此时总的商品购买量就为dp[i-1];
  • B:上一家( i - 1 )住户没有购买商品,所以第i户住户任然可以进行购买,此时总的商品购买量就为dp[i-2] + arr[i];

因为我们找的时最大的商品购买量,所以我们需要在这两种情况中选商品购买最多的那一个,即:
dp[i] = max{ dp[i-1] ,dp[i-2] + arr[i] }
这也就是我们的递推方程。

(3)给定初始条件和边界情况

不难看出,dp[0]和dp[1]不能从该递推方程中得出,所以 i 的取值范围就是[2,arr.length-1]。
我们再来给dp[0]和dp[1]赋值:
当推销到第一户住户时,最大购买量就是此住户的购买量,即dp[0] = arr[0];
当推销到第二户住户时,因为一户购买了,另一户就无法购买了,所以最大推销量就是取二者的购买量大的一方,即dp[1] = max{ arr[0],arr[1] }。

(4)总函数

综上所述,我们就可以得到下面这个函数:
在这里插入图片描述
我们的目标函数是:dp[arr.length - 1]

四、代码实现(Java)

图个方便,就不从用户输入接收数组arr了,直接在代码前new一个,感兴趣的可以自己在前面写一个读入的步骤

	//无标记版
	int[] arr = {2,2,5,1,9,5,6,7};
	int[] dp = new int[arr.length];
	dp[0] = arr[0];
	dp[1] = arr[0] > arr[1] ? arr[0] : arr[1];//三元运算符
	int max = 0;
	for(int i = 2; i < arr.length; i++) {
		dp[i] = dp[i-1] > dp[i-2]+arr[i] ? dp[i-1] : dp[i-2]+arr[i];//三元运算符
	}
	System.out.println(dp[arr.length - 1]);

运行一下:

23 //7+9+5+2=23

可以看到得出了正确答案,说明这个算法还是比较成功的。
我们来简单看一下过程是怎样进行的:
i=0: dp[0] = arr[0];
i=1: dp[1] = max{ arr[0],arr[1] } = max{2,2} = 2;
i=2: dp[2] = max{ dp[2-1] ,dp[2-2] + arr[2] } = max{2,2+5} = 7;
i=3: dp[3] = max{ dp[3-1] ,dp[3-2] + arr[3] } = max{7,2+1} = 7;
以此类推,我们就能得到每个dp[i]:
在这里插入图片描述
最后这个dp[arr.length - 1]也就是我们需要的最大商品购买量了。

五、指示函数

(1)定义数组及其含义

虽然已经输出了正确的最大商品数,但是我们还不知道是那几户购买了商品,所以我们需要对其增加一个标记用的数组 s[ ]。
其中s[i] 的含义就是到第i户时,上一个购买商品的住户是第几户。

(2)找出数组元素之间的关系式(递推方程)

而如何确定上一个购买的用户是第几户,我们需要函数完成dp[i] = max{ dp[i-1] ,dp[i-2] + arr[i] } 的判断后才能知道:
如果dp[i-1] > dp[i-2] + arr[i],那么上一户购买商品的就是第i-1户;
如果dp[i-1] <= dp[i-2] + arr[i],那么上一户购买商品的就是第i-2户。
所以我们就可以得到这样一个函数:

(3)给定初始条件和边界情况

取值范围:[2,arr.length-1]
对于s[0] 因为它前面就没有住户了,我们可以为其初始化为0;
对于s[1], 我们判断arr[0]和arr[1]的大小来判断:
如果arr[0] > arr[1] , s[1] = 0
如果arr[0] <= arr[1] , s[1] = 1

(4)实现代码(Java)

    //有标记版
	int[] arr = {2,2,5,1,9,5,6,7};
	int[] dp = new int[arr.length];
	int[] s = new int[arr.length];
	dp[0] = arr[0];
	s[0] = 0;
	if(arr[0] > arr[1]) {
		dp[1] = arr[0];
		s[1] = 0;
	}else {
		dp[1] = arr[1];
		s[1] = 1;
	}
	int max = 0;
	for(int i = 2; i < arr.length; i++) {
		if(dp[i-1] > dp[i-2]+arr[i]) {
			dp[i] = dp[i-1];
			s[i] = i-1;
		}else {
			dp[i] = dp[i-2]+arr[i];
			s[i] = i-2;
		}
	}
	System.out.println(dp[arr.length - 1]);
	
	for(int j = arr.length - 1; s[j] >= 0 ;) {
		if(j - s[j] == 2) {
			System.out.print(arr[j] + "+");
		}
		if(j == 0 || j == s[j]) {
			System.out.print(arr[j]);
			break;
		}
		j = s[j];
	}

执行一下:

23 //7+9+5+2=23
7+9+5+2

成功执行,我们还是来看一下进行的过程:
i=1: s[0] = 0;
i=1: arr[0] <= arr[1], 所以s[1] = 1;
i=2: dp[2-1] < dp[2-2] + arr[2],所以s[2] = i – 2 = 2 – 2 = 0
i=3: dp[3-1] < dp[3-2] + arr[3],所以s[3] = i -1 = 3 -1 = 2
以此类推,即可得到所有的s[i]:
在这里插入图片描述
可以看出当 j – s[j] == 2 时,说明这一户住户是购买了商品的,打印出来,此时 j 的值变动为s[j], 只需依次打印符合 j – s[j] == 2的值即可。

我们当然也可以稍微改动下打印的语句来得到更详细的信息:
在这里插入图片描述
好的,到此为止,一道动态规划的题就被我们解决了
开心
补充:

上课的时候老师问了下,这个指示函数打印为什么是当 j – s[j] == 2 时进行输出,因为原本确实是找规律看出来的,后来想了一下,也是有些许道理在里面的:

在(2)的函数中我们可以发现:
当i>2时, s[i] 与 i 的关系只有两种:s[i] = i-1 或者s[i] = i-2
在这里插入图片描述

在(1)中我们也说了s[i] 的含义就是到第i户时,上一个购买商品的住户是第几户。
因为推销规则中说到相邻的两户是不能一起买的,所以当s[i] = i-1时,说明上一个购买商品的就在前一户(i-1),所以这户(i)明显是没有进行购买的,我们无需输出。

只有当上一个购买的用户是i-2户时(即s[i] = i-2),才是真正购买的住户位置,或者说,此户(i)的购买才是有效的。

当i<=2时, s[0]恒为0s[1]为1或者为0
s[1] = 1时,说明购买商品的是第二户,此时的s[1] == 1;
s[1] = 0时,说明第一户是购买了的,我们应该输出第一户,此时s[0] == 0。
所以当 i<=2时,我们只需要输出s[i] == i 的住户即可。

以上内容谨为个人学习过程的记录,欢迎大家一起学习和指正

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

嘎嘎学编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值