一、题目描述
书籍的长、宽都是整数对应 (l,w)。如果书A的长宽度都比B长宽大时,则允许将B排列放在A上面。现在有一组规格的书籍,书籍叠放时要求书籍不能做旋转,请计算最多能有多少个规格书籍能叠放在一起。
二、输入输出描述
输入描述
- 二维整数数组
books,其中每个子数组[l, w]表示一本书的长度和宽度。
输出描述
- 一个整数,表示最多可叠放的书籍数量。
三、示例
|----|--------------------------------------------|
| 输入 | [[20,16],[15,11],[10,10],[9,10]] |
| 输出 | 3 |
| 说明 | |
四、解题思路
- 核心思想
将二维严格递增的书籍堆叠问题 转化为一维最长递增子序列(LIS)问题,通过 "排序固定一个维度 + 贪心 + 二分求解另一个维度的 LIS",高效计算最大堆叠数。核心是 "降维简化问题,贪心 + 二分优化 LIS 求解效率"。
- 问题本质分析
- 表层问题:找到能堆叠的最大书籍数量,要求下一层的长和宽严格大于上一层;
- 深层问题:
-
二维递增的等价转化:若先按长度升序排序(长度相同则宽度降序),则 "长度 + 宽度严格递增" 等价于 "宽度严格递增"(长度已满足递增,且长度相同时宽度降序避免重复);
-
LIS 的贪心优化:传统动态规划求解 LIS 的时间复杂度为 O (n²),而贪心 + 二分可降至 O (n log n),适合处理大数据量;
-
排序的关键作用:长度升序保证维度一的递增,长度相同宽度降序避免 "长度相同、宽度递增" 的书籍被错误计入 LIS(因为长度相同无法堆叠)。
-
核心逻辑
- 输入解析:将嵌套数组字符串转化为结构化的书籍数组;
- 排序降维:按 "长度升序、长度相同宽度降序" 排序,将二维问题转化为宽度的一维 LIS 问题;
- LIS 求解:用贪心 + 二分法计算宽度数组的最长递增子序列长度,该长度即为最大堆叠数。
-
步骤拆解
-
输入解析
- 读取嵌套数组字符串,通过正则分割 + 逐层截取,转化为
Integer[][]数组(每元素为[长度, 宽度])。
- 读取嵌套数组字符串,通过正则分割 + 逐层截取,转化为
-
排序降维
- 对书籍数组排序:
- 首要规则:长度升序(保证长度维度递增);
- 次要规则:长度相同时宽度降序(避免长度相同的书籍被计入同一 LIS);
- 提取排序后的宽度数组(仅需处理宽度的递增)。
-
贪心 + 二分求解 LIS
- 初始化
dp数组:存储 "长度为 i+1 的递增子序列的最小尾数"; - 遍历宽度数组:
- 若当前宽度 > dp 最后一个元素 → 追加到 dp,子序列长度 + 1;
- 若当前宽度 < dp 第一个元素 → 替换 dp 第一个元素,优化最短子序列尾数;
- 否则 → 二分找到 dp 中第一个≥当前宽度的位置,替换该位置元素(贪心优化);
dp的长度即为 LIS 的长度(最大堆叠数)。
- 初始化
-
结果输出
- 返回 LIS 长度,即为能堆叠的最大书籍数量。
五、代码实现
java
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String input = sc.nextLine();
// (?<=]),(?=\[) 正则表达式含义是:找这样一个逗号,前面跟着],后面跟着[
// 其中(?<=) 表示前面跟着
// 其中(?=) 表示后面跟着
Integer[][] books =
Arrays.stream(input.substring(1, input.length() - 1).split("(?<=]),(?=\\[)"))
.map(
s ->
Arrays.stream(s.substring(1, s.length() - 1).split(","))
.map(Integer::parseInt)
.toArray(Integer[]::new))
.toArray(Integer[][]::new);
System.out.println(getResult(books));
}
public static int getResult(Integer[][] books) {
// 长度升序,若长度相同,则宽度降序
Arrays.sort(books, (a, b) -> Objects.equals(a[0], b[0]) ? b[1] - a[1] : a[0] - b[0]);
Integer[] widths = Arrays.stream(books).map(book -> book[1]).toArray(Integer[]::new);
return getMaxLIS(widths);
}
// 最长递增子序列
public static int getMaxLIS(Integer[] nums) {
// dp数组元素dp[i]含义是:长度为i+1的最优子序列的尾数
ArrayList<Integer> dp = new ArrayList<>();
dp.add(nums[0]);
for (int i = 1; i < nums.length; i++) {
if (nums[i] > dp.get(dp.size() - 1)) {
dp.add(nums[i]);
continue;
}
if (nums[i] < dp.get(0)) {
dp.set(0, nums[i]);
continue;
}
int idx = Collections.binarySearch(dp, nums[i]);
if (idx < 0) dp.set(-idx - 1, nums[i]);
}
return dp.size();
}
}