算法区间合并问题

最近做了两道区间题,题目都不难,对比来看很有意思。两个都是区间合并的,不过两种合并方式,一种是并集,一种是交集。做完懵懵懂懂,有些感悟,写下来发现并没有自己以为的那么清楚,思考过程还是出了不少问题的。先记录下来吧,可能后面写多了就更清楚了。

先看第一道:

合并区间

以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间

思路:既然是区间,一定有 starti <= endi。首先一定会想到排序,优化一定是数据特殊或者问题特殊。排序后,数据就变得特殊了,可以进行下一步的优化。问题来了,怎么排序,是按 starti 还是 endi?先尝试用 starti 排序,排序后进行遍历,遇到重合就合并,也就是两个区间取并集。

javascript 复制代码
var merge = function (intervals) {
    // 以区间开始值为标准排序
    intervals.sort((a, b) => a[0] - b[0])
    let result = []
    let prev = null
    intervals.forEach(item => {
        const [start, end] = item
        let temp = [...item]
        if (prev) {
            const [prevStart, prevEnd] = prev
            // 重合,合并
            // start(i+1) <= endi
            if (start <= prevEnd) {
                temp[0] = prevStart
                // 注意这里,前一个区间可能完全包含当前区间
                // 需要取更大的那个区间 
                temp[1] = Math.max(end, prevEnd)
                // 合并完成,需要弹出被合并区间
                result.pop()
            }
        }
        result.push(temp)
        prev = temp
    })
    return result
};

能不能使用 endi 排序? 答案是可以,但是遍历顺序需要改变:

ini 复制代码
var merge = function (intervals) {
    intervals.sort((a, b) => a[1] - b[1])
    let result = []
    let prev = null
    let len = intervals.length
    // 从后向前遍历
    for (let i = len - 1; i >= 0; i--) {
        const [start, end] = intervals[i]
        let temp = [start, end]
        if (prev) {
            const [prevStart, prevEnd] = prev
            // 重合判断需要修改
            if (end >= prevStart) {
                temp[0] = Math.min(start, prevStart)
                temp[1] = prevEnd
                result.pop()
            }
        }
        result.push(temp)
        prev = temp
    }
    return result
};

简单说,这道题就是计算并集。合并时候,一定是相邻集合开始合并。

接下来是另一题:

用最少数量的箭引爆气球

有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points ,其中points[i] = [xstart, xend] 表示水平直径在 xstartxend之间的气球。你不知道气球的确切 y 坐标。

一支弓箭可以沿着 x 轴从不同点 完全垂直 地射出。在坐标 x 处射出一支箭,若有一个气球的直径的开始和结束坐标为 x``startx``end, 且满足 xstart ≤ x ≤ x``end,则该气球会被 引爆 。可以射出的弓箭的数量 没有限制 。 弓箭一旦被射出之后,可以无限地前进。

给你一个数组 points返回引爆所有气球所必须射出的 最小 弓箭数

能被一起射爆的,也就是区间重合的气球。和上一题不一样,计算的是交集。题目里说的很复杂,好像和Y轴有关系一样,实际上没什么关系,还是单一维度上区间的计算。

对比合并区间,难点在于能被一根箭引爆的所有区间,必须都有交集。例如 1, 2,2, 3,3, 4,第一个和第三个区间并没有交集,不能视为一组。

如果还是和上一道题一样,以 starti 排序,遍历是否可以?排序后同理,开始值递增,区间长度不定。决定期间重合的变成了上界,有 starti <=start(i+1),需要有 endi >= start(i+1),两者才能重合。

对于下图所示情况,0 位置,相交区间 [start0, end0]。1 位置,相交区间 [start1, end1]。2 位置,相交区间 [start2, end0]

其实无需纠结第二个区间到第三个区间的变化,既然有不重合的,一定会需要多一个。start2 > start1,后续区间一定和区间二不重合。对于和区间重合的区间,随你怎么去,反正重合不了。所以边界合并规则就有了,下限一定取最新收缩下边界,上边界取最小,没有重合就新增。

ini 复制代码
var findMinArrowShots = function (points) {
    // 按结尾排序,开始一定小于等于结尾
    points.sort((a, b) => a[0] - b[0]);
    let ans = 1; // 默认第一个区间必有一次
    let limit = points[0][1];
    // 开始值永远增加
    for (let i = 0; i < points.length; i++) {
        const p = points[i];
        const [start, end] = p;
        // 如果当前开始大于下限,增加
        if (start > limit) {
            ans++;
            // 次数已经增加,直接取最大上限
            limit = end;
        } else {
            // 考虑交集区间
            limit = Math.min(end, limit)
        }
    }
​
    return ans;
};

如果考虑用 endi 排序,决定是否有交集的变成了区间下界。从左到右,上界递增,最小的上界就是最左边的,不重合就增加即可。这种方法比上面要简单,优化在哪里我还要想想,先记录下来吧。

ini 复制代码
var findMinArrowShots = function (points) {
    // 按结尾排序,开始一定小于等于结尾
    points.sort((a, b) => a[1] - b[1]);
    let ans = 0;
    let limit = -Infinity; // 下限默认负无穷,一定会有一次
    for (let i = 0; i < points.length; i++) {
        const p = points[i];
        const [start, end] = p;
        // 如果当前开始大于下限,增加
        if (start > limit) {
            ans++;
            // 次数已经增加,直接取最大上限
            limit = end;
        }
    }
    return ans;
};

notice: 题目名称是 leetcode 链接,可以点击尝试自己完成一下。

相关推荐
子兮曰1 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰1 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
倒头就睡的小比特1 天前
算法竞赛C++常用的STL
c++·算法
小羊没烦恼!1 天前
初探性能优化——2个月到4小时的性能提升
java·开发语言·windows·算法·c#
前端小万1 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝1 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋1 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
猎头南楼1 天前
知识社区推荐系统实践:新用户冷启动与长短期兴趣建模的挑战 资深推荐算法工程师
人工智能·深度学习·算法·机器学习
卡布鲁1 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
旖旎夜光1 天前
力控面试题 01.01: 判定字符是否唯一(位运算) —— 题解
c++·学习·算法·leetcode·力控