【OD机试题多种解法笔记】书籍叠放

题目


书籍的长、宽都是整数对应 (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. 俄罗斯套娃信封问题。可用动态规划和二分查找解决。动态规划解法本质还是枚举吧,只是做了自底向上的存储子问题的解的优化操作;二分查找方案是借用最长递增子序列的优化解法,在长度严格递减情况下,查找宽度序列中的最长严格递减序列的长度即最后结果,不过在处理二维序列排序问题上需要特殊处理。

解法一:动态规划

思路
  1. 排序:先对书籍按长度从大到小排序,若长度相同则按宽度从大到小排序。

  2. 动态规划:定义 dp[i] 表示以第 i 本书为结尾的最长叠放序列长度。对于每本书 i,遍历前面所有书 j,若 books[j] 的长宽大于 books[i] 的长宽,则更新 dp[i] = max(dp[i], dp[j] + 1)

  3. 结果:取 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)

思路

  1. 排序:先按长度升序,再按宽度降序,为什么这样做?这个是在二维序列上查找最长严格递减子序列,如果第一维序列存在无法严格递增的部分比如[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,结果正确了!

  2. 二分查找:定义最长递增子序列数组list,遍历排序后的书籍列表,每一轮对list数组进行二分查找将当前书籍的宽度值插入数组list中合适的位置。这块实现照搬最长递增子序列的二分查找优化算法实现;

  3. 结果: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);
});


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值