数组的【合并区间】问题具体思路(附完整代码)


前言

本文将围绕数组【合并区间】问题展开讨论。

对于这一问题将采用多种思路方法来解决


一、如何理解数组的合并区间?

题目链接: [点击跳转] Leetcode 56. 合并区间

题目如下:

请添加图片描述

数组中的每个元素,都可以看作是一个整数范围,范围与范围之间可能存在重叠的部分,我们要做的就是把,重叠的范围变化成一个大的范围,相当于两个几个取并集。


二、方法一(比较排序)

观察题中所给的用例,我们可以分析出,需要合并的元素,存在一些特点:

某个范围的一个端值x(左或右)在另一个范围[a,b]内,也就是a<=x<=b

所以一条比较清晰的思路就出来了,遍历这个数组的所有元素,每个元素都有两个端值,逐个比较,再按照要求合并,最后将这个过程中的所有元素传入结果数组中即可。

但这个思路存在一些问题需要解决:

1.我们采用什么方式来比较不同元素的端值?

2.我们在进行过一个合并后,如果直接就传入到结果数组中,那么会不会忽略掉一些可能的情况,比如:要是连续遍历的三个范围都需要合并呢?如果合并两个就直接输出,那么就会漏掉需要合并的第三个范围。

3.怎样设计这个合并函数呢?

4.边界问题怎样处理?

解决方案:

1.正常情况下,由于数组中元素的不确定性导致我们必须得判断前一个范围的左右两端值在下一个范围元素的情况(!!!并且在这里也不可以直接就传入哟!!!因为我们不知道在后面会不会有隔了很远的范围元素还可以合并),所以我们可以先将数组中的所有范围做一个排序(范围左端值升序排序),然后在遍历时比较右端值在下一个范围的情况即可。

2.在合并后采用回溯,或者保持当前元素的位置,而不是直接做判断传入的动作。

3.合并函数是将确定需要合并的两个范围做合并动作,所有不再需要判断,直接取两范围最小值做新范围左值,取两范围最大值做新范围右值。

4.对于空数组,或空元素数组,或单一元素数组做特殊判断,同时也要注意数组末端的情况处理。

代码如下:

class Solution {
public:
    //合并
    vector<int> hebing(vector<int> A,vector<int> B) {
        return {min(A[0], B[0]), max(A[1], B[1])};
    }

    //升序
    static bool compareIntervals(const vector<int>& a, const vector<int>& b) {
        return a[0] < b[0];
    }

    vector<vector<int>> merge(vector<vector<int>>& intervals) {
        int n = intervals.size();
        vector<vector<int>> res;
        int a = INT_MAX;
        int b = INT_MIN;
        
        //传入空数组
        if (n == 0) {
            return res;
        }

        //只传一个数组
        if (n == 1) {
            return intervals;
        }

        // 先对区间按左端升序排序
        sort(intervals.begin(), intervals.end(), compareIntervals);

        vector<int> curr = intervals[0];
        for (int i = 1; i < n; i++) {
            vector<int> tre = intervals[i];
            if (tre[0] <= curr[1]) {
                // 合并
                curr = hebing(curr, tre);
            } else {
                res.push_back(curr);//将数组存入结果数组中
                curr = tre;
            }
        }

        res.push_back(curr);

        return res;
    }
};

三、方法二(扫描线算法)

上述代码通过先对区间按照左端升序排序,然后遍历区间进行合并操作来解决合并区间问题。时间复杂度主要由排序决定,为O(nlogn)。

若要换一种方法,可以考虑使用扫描线算法。其基本思路是将区间的起点和终点看作事件点,从左到右扫描这些事件点,记录当前重叠的区间数量。当重叠数量从 0 变为 1 时记录新的区间起始位置,当重叠数量从 1 变为 0 时记录新的区间结束位置。这种方法可以达到O(n)的时间复杂度。

代码如下:

vector<vector<int>> merge(vector<vector<int>>& intervals) {
    vector<vector<int>> result;
    // 用于存储事件点
    set<pair<int, int>> events;
    // 将区间的起点和终点加入事件点集合
    for (const auto& interval : intervals) {
        events.insert({interval[0], 1});
        events.insert({interval[1], -1});
    }

    int count = 0;
    int start = -1;
    // 扫描事件点
    for (const auto& event : events) {
        if (count == 0 && event.second == 1) {
            start = event.first;
        }
        count += event.second;
        if (count == 0 && event.second == -1) {
            result.push_back({start, event.first});
        }
    }
    return result;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值