【DP】2023B-书籍叠放
题目描述与示例
题目描述
书籍的长、宽都是整数对应(l,w)
。如果书A
的长宽度都比B
长宽大时,则允许将B
排列放在A
上面。
现在有一组规格的书籍,书籍叠放时要求书籍不能做旋转,请计算最多能有多少个规格书籍能叠放在一起。
输入描述
输入:books = 20,16,15,11,10,10,9,10
说明:总共4
本书籍,
第一本长度为20
,宽度为16
;
第二本书长度为15
宽度为11
;
依次类推,最后一本书长度为9
,宽度为10
输出描述
输出:3
最多能有多少个规格书籍能叠放在一起
示例一
输入
20,16,15,11,10,10,9,10
输出
3
说明
最多3
个规格的书籍可以叠放到一起,从下到上依次为: [20,16],[15,11],[10,10]
示例二
输入
20,15,15,20
输出
1
解题思路
注意:本题和LC354. 俄罗斯套娃信封问题、 面试题 17.08. 马戏团人塔、LC1626. 无矛盾的最佳球队等题目几乎完全一致,属于LC300. 最长递增子序列的轻变形题目。
如何转变为LIS问题
长度严格不等的情况
先考虑一种比较简单的情况,所有书本的长度两两不等,譬如用例
books = [(1, 1), (3, 2), (2, 4), (4, 3)]
我们可以把所有书本按照长度进行排列,得到
books = [(1, 1), (2, 4), (3, 2), (4, 3)]
对books
进行排序后,单看长度的话,后面位置的书本长度一定大于前面位置的书本长度,即一定存在books[i][0] > books[j][0]
对于任何i > j成立
。故我们只需要考虑宽度即可,把所有宽度从books
数组中提取出来可以得到宽度数组widths
widths = [1, 4, 2, 3]
为了使得叠放的书本尽可能多,子序列就要尽可能长。问题实际上转变为对宽度数组widths
考虑最长递增子序列LIS问题,即宽度数组能够取得的LIS长度,就是整个书本数组能够取得的LIS长度。故我们对widths
数组进行LIS问题的求解即可。
上述例子可以得到widths
的LIS是[1, 2, 3]
,对应books
的LIS是[(1, 1), (3, 2), (4, 3)]
,长度3
即为答案。
长度可能相等的情况
对于书本长度可能相等的情况呢?考虑用例
books = [(1, 1), (2, 3), (4, 4), (2, 2)]
如果我们考虑对长度进行升序排序后,对长度相等的书本根据宽度也进行升序排序,那么会得到
books = [(1, 1), (2, 2), (2, 3), (4, 4)]
提取宽度数组widths
得到
widths = [1, 2, 3, 4]
显然此时widths
的LIS是[1, 2, 3, 4]
,长度为4
,但其对应的books
数组的LIS并不能取[(1, 1), (2, 2), (2, 3), (4, 4)]
,因为长度同样为2
的书本的叠放并不符合要求。
为了避免上述问题,我们考虑对长度进行升序排序后,对长度相等的书本根据宽度进行降序排序,那么会得到
books = [(1, 1), (2, 3), (2, 2), (4, 4)]
提取宽度数组widths
得到
widths = [1, 3, 2, 4]
此时widths
的LIS是[1, 3, 4]
或者[1, 2, 4]
,对应的books
数组的LIS为[(1, 1), (2, 3), (4, 4)]
或者[(1, 1), (2, 2), (4, 4)]
,符合要求。
这是因为对于长度相等的书本而言,更小的宽度被排到更后的位置,这样当我们对宽度考虑LIS问题时,就不会出现长度相等的书籍出现在递增子序列中的情况了。
排序依据
故最终我们选择先依据长度升序,再依据宽度降序的方式,对书本数组books
进行排序
books.sort(key = lambda item: (item[0], -item[1]))
dp求解LIS问题
在对书本数组进行排序之后,剩下部分就是对所有书本宽度做常规的LIS问题了,此部分可以参考LC300. 最长递增子序列。
我们考虑动态规划三部曲:
dp
数组的含义是什么?
dp
数组是一个长度为n
的一维列表,dp[i]
表示以第i
本书籍为结尾的最长递增子序列的长度
- 动态转移方程是什么?
- 对于第
i
本书,我们都去考虑其前面的第i-1
本书,用索引j
表示。如果第i
本书的宽度大于第j
本书的宽度,即存在books[i][1] > books[j][1]
成立,那么i
可能可以放在j
的后面。 - 如果选择
i
放在j
的下方能使得i
叠放更多书籍,或者说递增子序列能够更长,那么我们选择j
。
for i in range(1, n)
for j in range(i):
if books[i][1] > books[j][1]:
dp[i] = max(dp[j]+1, dp[i])
dp
数组如何初始化?
- 以第
0
本书为结尾的最长递增子序列的长度为1
。其余书本对应的最长递增子序列长度也可以默认为1
。
dp = [1] * n
代码
# 题目:2023B-书籍叠放
# 分值:200
# 作者:许老师-闭着眼睛学数理化
# 算法:DP/LIS问题变种
# 代码看不懂的地方,请直接在群上提问
# 输入
lst = list(map(int, input().split(",")))
# 将输入长度为2n的一维列表转化为n*2的二维列表
# books[i] = (第i本书的长度,第i本书的宽度)
books = [(lst[i], lst[i+1]) for i in range(0, len(lst), 2)]
# 对n本书进行排序,先依照长度升序排序,长度相等时依据宽度降序排序
books.sort(key = lambda item: (item[0], -item[1]))
# 获得书本的数量n
n = len(books)
# 初始化dp数组
# dp[i]选择了第i本书籍为底部书籍,能够叠放的最多数目
# 或者说以第i本书籍为结尾的最长递增子序列的长度
dp = [1] * n
# 遍历每一本书籍i
for i in range(1, n):
# 考虑书籍i之前的所有书籍j
for j in range(i):
# 如果书籍i的宽度大于书籍j的宽度
# 那么书籍j可以放在书籍i的上方
# 如果选择i放在j的下方能使得i叠放更多书籍
# 则选择j
if books[i][1] > books[j][1]:
dp[i] = max(dp[j]+1, dp[i])
# dp数组中的最大值为能够叠放的最大书籍数目
print(max(dp))
时空复杂度
时间复杂度:O(N^2)
。dp过程需要进行双重循环,复杂度为O(N^2)
。排序的时间复杂度为O(NlogN)
。总的复杂度取其中的较大值,为O(N^2)
。
空间复杂度:O(N)
。dp数组所占空间。
华为OD算法冲刺训练
-
华为OD算法冲刺训练目前开始常态化报名!目前已服务100+同学成功上岸!
-
课程讲师为全网50w+粉丝编程博主@吴师兄学算法 以及小红书头部编程博主@闭着眼睛学数理化
-
每期人数维持在20人内,保证能够最大限度地满足到每一个同学的需求,达到和1v1同样的学习效果!
-
30+天陪伴式学习,20+直播课时,300+动画图解视频,200+LeetCode经典题,100+华为OD真题,还有简历修改与模拟面试将为你解锁
-
可查看链接 OD算法冲刺训练课程表 & OD真题汇总(持续更新)
-
绿色聊天软件戳
sheepvipvip
了解更多