【C/C++】2271. 毯子覆盖的最多白色砖块数

304 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第13天,点击查看活动详情


题目链接:2271. 毯子覆盖的最多白色砖块数

题目描述

给你一个二维整数数组 tiles ,其中 tiles[i]=[li,ri]tiles[i] = [l_i, r_i] ,表示所有在 lijril_i \leqslant j \leqslant r_i 之间的每个瓷砖位置 j 都被涂成了白色。

同时给你一个整数 carpetLen ,表示可以放在 任何位置 的一块毯子。

请你返回使用这块毯子,最多 可以盖住多少块瓷砖。

提示:

  • 1tiles.length51041 \leqslant tiles.length \leqslant 5 * 10^4
  • tiles[i].length==2tiles[i].length == 2
  • 1liri1091 \leqslant li \leqslant ri \leqslant 10^9
  • 1carpetLen1091 \leqslant carpetLen \leqslant 10^9
  • tiles 互相 不会重叠 。

示例 1:

example1drawio3.png

输入:tiles = [[1,5],[10,11],[12,18],[20,25],[30,32]], carpetLen = 10
输出:9
解释:将毯子从瓷砖 10 开始放置。
总共覆盖 9 块瓷砖,所以返回 9 。
注意可能有其他方案也可以覆盖 9 块瓷砖。
可以看出,瓷砖无法覆盖超过 9 块瓷砖。

示例 2:

example2drawio.png

输入:tiles = [[10,11],[1,1]], carpetLen = 2
输出:2
解释:将毯子从瓷砖 10 开始放置。
总共覆盖 2 块瓷砖,所以我们返回 2

整理题意

题目给定数组 tiles 表示 n 块瓷砖的起始和结束位置(且题目保证瓷砖不会重叠),这些瓷砖排成一条直线,现在给定一块长度为 carpetLen 的毯子,问最多能够覆盖多少块瓷砖。

解题思路分析

首先观察题目数据范围:

  • 瓷砖数量在 51045 * 10^4 以内,O(n2)O(n^2) 的暴力解决是行不通的,会超时 TLE
  • 起始和结束的位置和毯子的长度在 10910^9 以内,遍历起始和结束位置以及毯子长度同样是会超时的,且无法使用连续数组保存所有起始和结束的位置。

需要特别注意的是题目给的瓷砖起始位置并不是按照顺序给我们的,所以我们需要先对瓷砖按照起始位置进行排序。

方法一:排序 + 滑动窗口(双指针)

  1. 首先考虑对于一块瓷砖来说,如何覆盖最优,从当前瓷砖起始位置开始覆盖最优。对于当前瓷砖来说,我们右移毯子,会使得左边所覆盖的瓷砖数量减少一个,但右边多出来的毯子长度却不一定能够保证覆盖上一个,也就右移不会使得覆盖数量增加。
  2. 所以我们可以枚举每块瓷砖的起始位置作为毯子的覆盖起点,然后就可以得到毯子所能覆盖的终点位置。
  3. 完全符合 滑动窗口(双指针) 模型,我们可以把毯子长度看作区间长度,不断枚举毯子的起始位置,维护毯子所能够覆盖到的瓷砖数量最大即可。

方法二:排序 + 二分

我们还可以采用二分的方法得到毯子所覆盖的瓷砖数量,同样是排序后从小到大枚举以每块瓷砖的起始位置作为毯子的覆盖起点,二分毯子所能够覆盖到的位置,计算所覆盖的瓷砖数量,不断维护最大覆盖数量即可。

但由于枚举后二分的时间复杂度为 O(nlogn)O(n \log n),在时间复杂度上略差于滑动窗口(双指针)O(n)O(n),但也是足够通过题目的,在这里只是在思路上进行讲解,就不继续介绍具体实现和代码实现了。

具体实现

方法一:滑动窗口(双指针)

  1. 按照瓷砖起始位置升序排序。
  2. 按照从小到大的顺序枚举每个瓷砖的起始位置(也就是左区间)作为毯子覆盖的起点。
  3. 维护更新此时毯子所能覆盖到哪一块瓷砖,也就是毯子的结束位置(右区间)。(由于按照从小到大枚举,所以覆盖的右区间也是只增不减,总体时间复杂度为 O(n)O(n)。)
  4. 不断维护毯子所能覆盖的瓷砖数量,取最大值。

复杂度分析

  • 时间复杂度:O(nlogn)O(n\log n),其中 n 为瓷砖 tiles 的数量。对 tiles 数组排序的时间复杂度为 O(nlogn)O(n\log n),双指针维护最多可覆盖数量的时间复杂度为 O(n)O(n)
  • 空间复杂度:O(logn)O(logn),即为排序的栈空间开销

代码实现

方法一:滑动窗口(双指针)

class Solution {
public:
    int maximumWhiteTiles(vector<vector<int>>& tiles, int carpetLen) {
        //对瓷砖按照起始位置进行排序,使用默认排序
        sort(tiles.begin(), tiles.end());
        /* 使用自定义排序超时TLE,外置cmp也会超时TLE【已解决】
        sort(tiles.begin(), tiles.end(), [&](vector<int> A, vector<int> B){
            return A[0] < B[0];
        });
       //正确写法:[](vector<int> &A, vector<int> &B)
       //解释:[&]没有用到外部引用可以省略,&加上引用符合,避免不必要的拷贝,费时
        */
        int n = tiles.size();
        // ans 记录答案,最大覆盖数量
        int ans = 0;
        // now 记录当前完整覆盖数量
        int now = 0;
        // l 为覆盖左区间,r 为覆盖右区间
        int l = 0, r = 0;
        while(l < n){
            //当l不为0时减去上一块瓷砖数量
            if(l) now -= tiles[l - 1][1] - tiles[l - 1][0] + 1;
            //卡常优化 len记录毯子覆盖的右区间
            int len = tiles[l][0] + carpetLen - 1;
            while(r < n && tiles[r][1] <= len){
                now += tiles[r][1] - tiles[r][0] + 1;
                r++;
            }
            // 后面没有瓷砖了,更新答案后返回
            if(r == n){
                ans = max(ans, now);
                return ans;
            }
            //能够覆盖第 r 块瓷砖部分,注意可能没有覆盖则取 0
            int last = max(0, tiles[l][0] + carpetLen - tiles[r][0]);
            //维护答案最大值
            ans = max(ans, now + last);
            //继续枚举下一块瓷砖
            l++;
        }
        return ans;
    }
};

总结

  • 该题由于数据范围较大,首先需要想到以 每块瓷砖的起始位置作为毯子覆盖的起点 作为突破点。
  • 其次采用 滑动窗口双指针 的思想维护覆盖瓷砖数量。
  • 最后需要注意排序时如果采用自定义排序需要注意写法,否则会卡常数超时(TLE)。
  • 另外该题还可以使用排序后枚举加二分的方法,但由于其时间复杂度上略差于滑动窗口(双指针),所以不是该题的最优解,但由于都需要排序和枚举,总体时间复杂度都在 O(nlogn)O(n \log n) 级别。二分的思想也是需要了解和掌握的。
  • 测试结果: 微信截图_20220606224611.png

结束语

每天听课读书或每天沉迷手机,每天健身跑步或每天胡吃海塞,每天严格规划时间或每天懒懒散散,人与人之间的差距就在这一点一滴的小事中拉开。你未来的样子正是由当下的你决定的。你的努力,时间看得见。