算法区间合并问题

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

先看第一道:

合并区间

以数组 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]

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

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 分钟前
大厂算法面试 7 天冲刺:第5天- 递归与动态规划深度解析 - 高频面试算法 & Java 实战
算法·面试·动态规划
斯~内克1 小时前
Electron 菜单系统深度解析:从基础到高级实践
前端·javascript·electron
数据知道2 小时前
【YAML】一文掌握 YAML 的详细用法(YAML 备忘速查)
前端·yaml
dr李四维2 小时前
vue生命周期、钩子以及跨域问题简介
前端·javascript·vue.js·websocket·跨域问题·vue生命周期·钩子函数
invincible_Tang2 小时前
R格式 (15届B) 高精度
开发语言·算法·r语言
旭久2 小时前
react+antd中做一个外部按钮新增 表格内部本地新增一条数据并且支持编辑删除(无难度上手)
前端·javascript·react.js
windyrain2 小时前
ant design pro 模版简化工具
前端·react.js·ant design
浪遏2 小时前
我的远程实习(六) | 一个demo讲清Auth.js国外平台登录鉴权👈|nextjs
前端·面试·next.js
独好紫罗兰2 小时前
洛谷题单2-P5715 【深基3.例8】三位数排序-python-流程图重构
开发语言·python·算法
GISer_Jing2 小时前
React-Markdown详解
前端·react.js·前端框架