题目
书籍的长、宽都是整数对应 (l,w)。如果书 A 的长宽都比 B 长宽大时,则允许将 B 排列放在 A 上面。现在有一组规格的书籍,书籍叠放时要求书籍不能做旋转,请计算最多能有多少个规格书籍能叠放在一起。
输入描述
输入: books = [[20,16],[15,11],[10,10],[9,10]]
说明:总共 4 本书籍,第一本长度为 20 宽度为 16;第二本书长度为 15 宽度为 11,依次类推,最后一本书长度为 9 宽度为 10。
输出描述
输出: 3
说明:最多 3 个规格的书籍可以叠放到一起,从下到上依次为: [20,16],[15,11],[10,10]
用例
输入 | 输出 |
---|---|
[[20,16],[15,11],[10,10],[9,10]] | 3 |
思考
题目改自LeetCode 354. 俄罗斯套娃信封问题。可用动态规划和二分查找解决。动态规划解法本质还是枚举吧,只是做了自底向上的存储子问题的解的优化操作;二分查找方案是借用最长递增子序列的优化解法,在长度严格递减情况下,查找宽度序列中的最长严格递减序列的长度即最后结果,不过在处理二维序列排序问题上需要特殊处理。
解法一:动态规划
思路
-
排序:先对书籍按长度从大到小排序,若长度相同则按宽度从大到小排序。
-
动态规划:定义
dp[i]
表示以第i
本书为结尾的最长叠放序列长度。对于每本书i
,遍历前面所有书j
,若books[j]
的长宽大于books[i]
的长宽,则更新dp[i] = max(dp[i], dp[j] + 1)
。 -
结果:取
dp
数组中的最大值作为答案。
示例分析
-
输入排序后: [20,16]],[15,11],[10,10],[[9,10]。
- 计算
dp
数组:-
dp[0] = 1
(单独一本书)。 -
dp[1]
:前面只有[20,16]
,长宽均大于,故dp[1] = dp[0] + 1 = 2
。 -
dp[2]
:前面[15, 11
]
,长宽均大于 [10,10],故dp[2] = dp[1] + 1 = 3。
-
dp[3]
:前面[15,11]
的长宽大于自身,取最大dp[1]+1=2+1=3
。 -
最终结果为 Max(dp[0], dp[1], dp[2], dp[3]) =
3
。
-
时间复杂度
-
排序时间:
O(n log n)
。 -
动态规划时间:
O(n²)
,适用于n
较小的情况(如n ≤ 1000
)。
参考代码
function solution(books) {
let n = books.length;
// 按长度降序,长度相同按宽度降序
books.sort((a, b) => b[0] - a[0] || b[1] - a[1]);
// dp[i] 表示以第i本书结尾的最长叠放序列长度
const dp = Array(n).fill(1);
for (let i = 1; i < n; i++) {
for (let j = 0; j < i; j++) {
if (books[j][0] > books[i][0] && books[j][1] > books[i][1]) {
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
}
console.log(Math.max(...dp));
}
const cases = [
[[20,16],[15,11],[10,10],[9,10]],
[[20,5],[15,4],[10,6],[9,7],[8,2]],
[[5,5], [5,3], [4,6], [4,4]]
];
cases.forEach(obj => {
solution(obj);
});
解法二:二分查找(优化版 LIS)
思路
-
排序:先按长度升序,再按宽度降序,为什么这样做?这个是在二维序列上查找最长严格递减子序列,如果第一维序列存在无法严格递增的部分比如[5, 5, 3, 1],那么再在第二维递减序列上查找最长递减子序列就会出错,举个例子,输入书籍: [[5, 2], [5, 4],[4,3]],这个输出结果应该是1,因为长度都相等,不满足严格递减,但由于先按长度递减排序再按宽度递减排序,查找宽度序列[4, 3, 2]得出最长递减子序列长度是3,显然答案不对。但原序列按宽度降序处理后为[[5,4][5,3],[5,2],再在宽度序列[4,3,2]上查找最长递增子序列长度是1,结果正确了!
-
二分查找:定义最长递增子序列数组list,遍历排序后的书籍列表,每一轮对list数组进行二分查找将当前书籍的宽度值插入数组list中合适的位置。这块实现照搬最长递增子序列的二分查找优化算法实现;
-
结果:list的长度即为最长叠放序列长度。
时间复杂度
-
排序时间:
O(n log n)
。 -
二分查找时间:
O(n log n)
,适用于n
较大的情况(如n ≤ 1e5
)。
参考代码
function solution(books) {
books.sort((a, b) => a[0] - b[0] || b[1] - a[1]); // 按长度升序,按宽度降序
const list = []; // 存放宽度数组中的最长递增子序列
for (const book of books) {
let l = 0, h = list.length, w = book[1];
while (l < h) {
const m = l + Math.floor((h-l)/2);
if (list[m] >= w) {
h = m;
} else {
l = m + 1;
}
}
if (l === list.length) {
list.push(w);
} else {
list[l] = w;
}
}
console.log(list.length);
}
const cases = [
[[20,16],[15,11],[10,10],[9,10]],
[[20,5],[15,4],[10,6],[9,7],[8,2]],
[[5,5], [5,3], [4,6], [4,4]]
];
cases.forEach(obj => {
solution(obj);
});