算法区间合并问题

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

先看第一道:

合并区间

以数组 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 链接,可以点击尝试自己完成一下。

相关推荐
UP_Continue13 分钟前
排序--计数排序
数据结构·算法
小着1 小时前
vue项目页面最底部出现乱码
前端·javascript·vue.js·前端框架
牵手夏日3 小时前
题目类型——左右逢源
算法
愚润求学4 小时前
【递归、搜索与回溯】FloodFill算法(一)
c++·算法·leetcode
lichenyang4534 小时前
React ajax中的跨域以及代理服务器
前端·react.js·ajax
呆呆的小草4 小时前
Cesium距离测量、角度测量、面积测量
开发语言·前端·javascript
一 乐5 小时前
民宿|基于java的民宿推荐系统(源码+数据库+文档)
java·前端·数据库·vue.js·论文·源码
sunny-ll5 小时前
【C++】详解vector二维数组的全部操作(超细图例解析!!!)
c语言·开发语言·c++·算法·面试
testleaf6 小时前
前端面经整理【1】
前端·面试
好了来看下一题6 小时前
使用 React+Vite+Electron 搭建桌面应用
前端·react.js·electron