算法区间合并问题

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

先看第一道:

合并区间

以数组 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] 表示水平直径在 xstart 和 xend之间的气球。你不知道气球的确切 y 坐标。

一支弓箭可以沿着 x 轴从不同点 完全垂直 地射出。在坐标 x 处射出一支箭,若有一个气球的直径的开始和结束坐标为 x``start,x``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 链接,可以点击尝试自己完成一下。

相关推荐
Moment9 小时前
代码都能让 Agent 写了,我们为什么还要学前端?
前端·后端·面试
Hilaku9 小时前
MCP 协议会成为前端 AI 工程的标准接口吗?
前端·javascript·程序员
Zan10 小时前
Tauri 2 实战:为什么一个可靠的桌面应用,应该由 Rust Host 管理生命周期?
前端·架构·rust
SameX10 小时前
鸿蒙 ArkTS 独立开发者的项目结构 model / dao / viewmodel / page / common 的分层
前端
阿星AI工作室10 小时前
WorkBuddy做数字人口播:动效包装+克隆声音+真实照片+对口型全流程
前端
火山引擎开发者社区10 小时前
给企业配一双 AI 的手:火山引擎 OS Agent 替你操作手机和电脑
前端·人工智能
三年三月10 小时前
自建https证书SSL
前端
心仪久了会沦陷11 小时前
C语言数据结构入门——树与二叉树详解:概念、堆实现与链式遍历
c语言·数据结构·算法
小此方11 小时前
[算法实战论:动态规划]背包问题概念,01不满背包问题,01满背包问题,背包问题的滚动数组空间优化
算法·动态规划
ClouGence11 小时前
Web 自动化测试总是超时?聊聊等待、超时和异步加载
前端·测试