Python 算法题之 俄罗斯套娃信封
给出题目🍠
有一个二维整数数组 envelopes
,其中 envelopes
中的每个值代表着一个信封,每个信封的宽度和高度以整数对 [ w, h ] 表示,当另一个信封的宽度和高度都比这个信封大的时候,这个信封就可以放进另一个信封里,如同俄罗斯套娃一样
计算 最多有多少个信封能组成一组 “俄罗斯套娃” 信封
比如输入
envelopes = [[5,4],[6,4],[6,7],[2,3]]
,能组成最多套娃信封组是 [2,3] => [5,4] => [6,7],算法应当输出 3
展示一个错误示例
这是我曾经落入的坑,我决定把它拿出来用于参考,让失败品也有其价值
思路:
- 先将二维整数数组
envelopes
依据 w 为第一参考值进行降序排序,如果 w 值重复,则依据 h 为第二参考值进行降序排序 - 从索引二 开始循环
envelopes
,预先存储 envelopes 第一个值new_envs = [envelopes[0]]
,如果先前值envelopes[-1]
的w
与 当前循环值envelopes[i]
的w
相同,根据排序后的结果,这个当前循环值就是相同宽度不同长度的信封 且 比上一个信封的h
小,排除这个信封 - 在循环中判断先前值
envelopes[-1]
的h
是否比当前循环值envelopes[i]
的小,着意味着大信封套不进小信封里,排除这个信封
结合上面的思路,写出代码
def max_envelopes(envelopes: List[List[int]]) -> int:
length = len(envelopes)
envelopes.sort(key=lambda x: (x[0], x[1]), reverse=True)
new_envs = [envelopes[0]]
for i in range(1, length):
# if 当前值的w与上一个值的w不重复 and 当前值的h比上一个值的h小
if new_envs[-1][0] != envelopes[i][0] and new_envs[-1][1] >= envelopes[i][1]:
new_envs.append(envelopes[i])
print(new_envs) # 查看挑选后的信封
return len(new_envs)
问题:
-
如果在
envelopes
排序后,每个信封的h
值都比后面的值大,确实可以解决问题 -
可如果排序后出现了某个信封
w
值虽然比后面的值都大,但h
值比后面的值小,套了这个信封将会导致原本后面可以套进的信封被这个异类 h 值信封拒之门外。要明确的是从这一堆信封中挑选能套娃最多的信封数,而不是依赖排序后的结果从前往后套
动态规划方法
关键点:
- 根据上面那错误示例的展示,可以得知此问题是需要通过排序解决,
envelopes
依据w
为第一参考值进行升序排序,如果w
值重复,则依据h
为第二参考值进行降序排序 - 排序后认证观察每个信封的
h
,其实就是一个标准的 LIS 问题,小的信封装进大的信封里 - 需要注意的是因为问题是给信封套娃,那在相同的宽度
w
信封之间,只有高度h
值最大的信封能选入递增子序列,这也是为什么在排序时h
需要进行降序排序的原因,确保最终信封序列不会出现相同 w 被选入的情况
- 合法的套娃是大的套小的,不存在小的套大的
def max_envelopes(envelopes: list) -> int:
if not envelopes:
return 0
length = len(envelopes)
# 排列规则:对宽度 w 进行升序排序,如果 w 相同时,则按高度 h 降序排序
envelopes.sort(key=lambda x: (x[0], -x[1]))
dp = [1] * length
# 小优化,从1开始循环,dp[0]为最小情况1,即只有自己一个的最长递增子序列
for i in range(1, length):
for k in range(i):
if envelopes[i][1] > envelopes[k][1]:
dp[i] = max(dp[i], dp[k] + 1)
return max(dp)
- 时间复杂度: O(n㏒n)
- 空间复杂度: O(n)
动态规划-全升序 方法
如果就是要 (w, h)
两者都全升序排列,又要能够正确获取答案,这可行吗?答案是可以的
再回去仔细看看上面的 关于 h
降序原因图 ,降序的原因是为了确保相同宽度 w
的信封中只有 h
最大的被选中,其余因为 h
值比先前的小而被 跳过(关键字)
目的是跳过相同宽度 w
的信封,除了降序能跳过外,还可以自己在循环中再加个判断 envelopes[i][0] > envelopes[k][0]
,就多了这段代码,就解决了此问题
def max_envelopes(envelopes: list) -> int:
if not envelopes:
return 0
length = len(envelopes)
# 排列规则:对宽度 w 进行升序排序,如果 w 相同时,则按高度 h 降序排序
# 全升序排列
envelopes.sort(key=lambda x: (x[0], x[1]))
dp = [1] * length
# 小优化,从1开始循环,dp[0]为最小情况1,即只有自己一个的最长递增子序列
for i in range(1, length):
for k in range(i):
if envelopes[i][1] > envelopes[k][1] and envelopes[i][0] > envelopes[k][0]:
dp[i] = max(dp[i], dp[k] + 1)
return max(dp)
此方法效率会比 h
降序方法低点,但有助于去理解算法
- 时间复杂度: O(n㏒n)
- 空间复杂度: O(n)
总结
- 这个问题难就难在有没有想到使用排序,正确排序后此问题可以通过 动态规划 LIS 解决
- 如果在解决这个问题的时候没有能想到排序后再处理,确实会出现无从下手的情况,那么通过这次可以启示我们,再遇到类似问题时,不妨先对数组排序试试
参考资料
- 题目出之力扣: 俄罗斯套娃信封 leetcode 354
- 感谢题解里 负雪明烛 大佬 全升序解法的启发
- 书籍:
- labuladong大佬 著作 的 《labuladong 的算法小抄》
这是一本很厉害的书,十分推荐
- labuladong大佬 著作 的 《labuladong 的算法小抄》