书籍叠放问题

一、题目描述

书籍的长、宽都是整数对应 (l,w)。如果书A的长宽度都比B长宽大时,则允许将B排列放在A上面。现在有一组规格的书籍,书籍叠放时要求书籍不能做旋转,请计算最多能有多少个规格书籍能叠放在一起。

二、输入输出描述

输入描述

  • 二维整数数组 books,其中每个子数组 [l, w] 表示一本书的长度和宽度。

输出描述

  • 一个整数,表示最多可叠放的书籍数量。

三、示例

|----|--------------------------------------------|
| 输入 | [[20,16],[15,11],[10,10],[9,10]] |
| 输出 | 3 |
| 说明 | |

四、解题思路

  1. 核心思想

二维严格递增的书籍堆叠问题 转化为一维最长递增子序列(LIS)问题,通过 "排序固定一个维度 + 贪心 + 二分求解另一个维度的 LIS",高效计算最大堆叠数。核心是 "降维简化问题,贪心 + 二分优化 LIS 求解效率"。

  1. 问题本质分析
  • 表层问题:找到能堆叠的最大书籍数量,要求下一层的长和宽严格大于上一层;
  • 深层问题:
  1. 二维递增的等价转化:若先按长度升序排序(长度相同则宽度降序),则 "长度 + 宽度严格递增" 等价于 "宽度严格递增"(长度已满足递增,且长度相同时宽度降序避免重复);

  2. LIS 的贪心优化:传统动态规划求解 LIS 的时间复杂度为 O (n²),而贪心 + 二分可降至 O (n log n),适合处理大数据量;

  3. 排序的关键作用:长度升序保证维度一的递增,长度相同宽度降序避免 "长度相同、宽度递增" 的书籍被错误计入 LIS(因为长度相同无法堆叠)。

  4. 核心逻辑

  • 输入解析:将嵌套数组字符串转化为结构化的书籍数组;
  • 排序降维:按 "长度升序、长度相同宽度降序" 排序,将二维问题转化为宽度的一维 LIS 问题;
  • LIS 求解:用贪心 + 二分法计算宽度数组的最长递增子序列长度,该长度即为最大堆叠数。
  1. 步骤拆解

  2. 输入解析

    • 读取嵌套数组字符串,通过正则分割 + 逐层截取,转化为Integer[][]数组(每元素为[长度, 宽度])。
  3. 排序降维

    • 对书籍数组排序:
    • 首要规则:长度升序(保证长度维度递增);
    • 次要规则:长度相同时宽度降序(避免长度相同的书籍被计入同一 LIS);
    • 提取排序后的宽度数组(仅需处理宽度的递增)。
  4. 贪心 + 二分求解 LIS

    • 初始化dp数组:存储 "长度为 i+1 的递增子序列的最小尾数";
    • 遍历宽度数组:
    1. 若当前宽度 > dp 最后一个元素 → 追加到 dp,子序列长度 + 1;
    2. 若当前宽度 < dp 第一个元素 → 替换 dp 第一个元素,优化最短子序列尾数;
    3. 否则 → 二分找到 dp 中第一个≥当前宽度的位置,替换该位置元素(贪心优化);
    • dp的长度即为 LIS 的长度(最大堆叠数)。
  5. 结果输出

    • 返回 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();
  }
}
相关推荐
测试开发Kevin11 小时前
小tip:换行符CRLF 和 LF 的区别以及二者在实际项目中的影响
java·开发语言·python
笨手笨脚の11 小时前
Redis: Thread limit exceeded replacing blocked worker
java·redis·forkjoin·thread limit
Abona11 小时前
C语言嵌入式全栈Demo
linux·c语言·面试
Lenyiin11 小时前
Linux 基础IO
java·linux·服务器
前端菜鸟日常11 小时前
鸿蒙开发实战:100 个项目疑难杂症汇编
汇编·华为·harmonyos
松☆12 小时前
Dart 核心语法精讲:从空安全到流程控制(3)
android·java·开发语言
编码者卢布12 小时前
【App Service】Java应用上传文件功能部署在App Service Windows上报错 413 Payload Too Large
java·开发语言·windows
吴维炜12 小时前
「Python算法」计费引擎系统SKILL.md
python·算法·agent·skill.md·vb coding
q行12 小时前
Spring概述(含单例设计模式和工厂设计模式)
java·spring