【算法-面试】区间专题

1. 区间问题

思路

主要有两个技巧

  1. 排序:常见的排序方法就是按照区间起点排序,或者先按照起点升序排序,若起点相同,则按照终点降序排序。当然,如果你非要按照终点排序,无非对称操作,本质都是一样的。

  2. 画图:就是说不要偷懒,勤动手,两个区间的相对位置到底有几种可能,不同的相对位置我们的代码应该怎么去处理。

leetcode题目

  • 56 合并区间
  • 986 区间列表的交集
  • 1288 删除被覆盖区间
  • 435 ⽆重叠区间
  • 452 ⽤最少数量的箭引爆⽓球
  • 1024 视频拼接

2. 题目

【 56 合并区间】

def merge(intervals):
    '''
    以数组 intervals 表示若⼲个区间的集合,其中单个区间为 intervals[i] = [starti, endi],请你将所有重叠的区间合并后返回。
    leetcode:56. 合并区间
    input: intervals = [[1,3],[2,6],[8,10],[15,18]]
    output: [[1,6],[8,10],[15,18]]
    思路:
        1.
        2.
        3.
    '''
    if not intervals:
        return []

    intervals = sorted(intervals, key=lambda x: x[0])
    res = [intervals[0]]
    for i in range(1, len(intervals)):
        cur = intervals[i]
        # res 中最后一个元素的引用
        last = res[-1]
        if cur[0] <= last[1]:
            last[1] = max(last[1], cur[1])
        else:
            res.append(cur)
    print(res)
    return res

【 986 区间列表的交集】

def intervalIntersection(firstList, secondList):
    '''
    给定两个由⼀些 闭区间 组成的列表,firstList 和 secondList,其中 firstList[i] = [starti, endi] ⽽ secondList[j] = [startj, endj]。
    每个区间列表都是成对 不相交 的,并且 已经排序。 返回这 两个区间列表的交集。
    形式上,闭区间 [a, b](其中 a <= b)表示实数 x 的集合,⽽ a <= x <= b。
    两个闭区间的 交集是⼀组实数,要么为空集,要么为闭区间。例如,[1, 3] 和 [2, 4] 的交集为 [2, 3]。
    leetcode: 986. 区间列表的交集
    input::firstList = [[0,2],[5,10],[13,23],[24,25]], secondList = [[1,5],[8,12],[15,24],[25,26]]
    output: [[1,2],[5,5],[8,10],[15,23],[24,24],[25,25]]
    思路:
        1.
        2.
        3.
    '''
    i, j = 0, 0
    res = []
    while i < len(firstList) and j < len(secondList):
        a1, a2 = firstList[i][0], firstList[i][1]
        b1, b2 = secondList[j][0], secondList[j][1]

        # 两个list判定有交集的前提,
        if b2 >= a1 and a2 >= b1:
            res.append([max(a1, b1), min(a2, b2)])

        # 两个list前进的步伐
        if b2 < a2:
            j += 1
        else:
            i += 1
    return res

【1288 删除被覆盖区间】

def removeCoveredIntervals(intervals):
    '''
    给你⼀个区间列表,请你删除列表中被其他区间所覆盖的区间。
    只有当 c <= a 且 b <= d 时,我们才认为区间 [a,b) 被区间 [c,d) 覆盖。
    在完成所有删除操作后,请你返回列表中剩余区间的数⽬。
    leetcode: 1288. 删除被覆盖区间
    input: intervals = [[1,4],[3,6],[2,8]]
    output: 2
    思路:
        1. 先将列表的起点按升序排序,然后将终点按照降序排序
        2. 根据三种情况
            1) 区间完全包含:此时需要删除的区间数+1
            2) 区间半包含:此时需要更新right的值为右侧更大的值
            3) 区间未包含:此时需要更新left和right值
        3. 最后剩余数目就是原数减去删除的数目
    '''
    #  起点按照升序排序,终点按照降序排序
    intervals = sorted(intervals, key=lambda x: (x[0], -x[1]))

    left, right = intervals[0][0], intervals[0][1]
    res = 0

    for i in range(1, len(intervals)):
        v = intervals[i]
        if left <= v[0] and right >= v[1]:
            res += 1  # 需要删除的区间+1
        if v[0] <= right <= v[1]:
            right = v[1]
        if right < v[0]:
            left = v[0]
            right = v[1]

    return len(intervals) - res

【435 ⽆重叠区间】

def eraseOverlapIntervals(intervals):
    '''
    给定⼀个区间的集合,计算需要移除区间的最⼩数量,使剩余区间互不重叠。
    leetcode: 435. ⽆重叠区间
    input:
    output:
    思路:
        1.
        2.
        3.
    '''
    if len(intervals) == 0:
        return 0
    intervals = sorted(intervals, key=lambda x: x[1])

    count = 1
    end = intervals[0][1]
    for i in range(1, len(intervals)):
        start = intervals[i][0]
        if start >= end:
            count += 1
            end = intervals[i][1]
    return len(intervals) - count

【452 ⽤最少数量的箭引爆⽓球】


def findMinArrowShots(intervals):
    '''
    在⼆维空间中有许多圆形的⽓球,⼀个⽓球在 x 轴上的投影为⼀个坐标区间 [start, end]。
    ⼀⽀⼸箭可以沿着 x 轴从不同点垂直向上射出,如果在坐标 x 处射出⼀⽀箭,所有 start <= x <= end的⽓球都会被射爆。
    给你⼀个数组 points,其中 points[i] = [start_i, end_i] 表示第 i 个⽓球的位置,可以射出的⼸箭的数量没有限制,计算引爆所有⽓球所必须射出的最⼩⼸箭数
    leetcode: 452. ⽤最少数量的箭引爆⽓球
    input: [[10,16],[2,8],[1,6],[7,12]]
    output:2
    思路:
        1.
        2.
        3.
    '''
    intervals = sorted(intervals, key=lambda x: x[1])
    count, end = 1, intervals[0][1]  # count初始化的时候,设置为1,至少需要一根箭
    for i in range(1, len(intervals)):
        start = intervals[i][0]
        if start >= end:  # 说明需要一支箭
            count += 1
            end = intervals[i][1]
    return count

【1024 视频拼接】

def videoStitching(clips, T):
    '''
    你将会获得⼀系列视频⽚段,这些⽚段来⾃于⼀项持续时⻓为 T 秒的体育赛事。 这些⽚段可能有所重叠,也可能⻓度不⼀。
    视频⽚段 clips[i] 都⽤区间进⾏表示:开始于 clips[i][0] 并于 clips[i][1] 结束。
    我们甚⾄可以对这些⽚段⾃由地再剪辑,例如⽚段 [0, 7] 可以剪切成 [0, 1] + [1, 3] + [3, 7] 三部分。
    我们需要将这些⽚段进⾏再剪辑,并将剪辑后的内容拼接成覆盖整个运动过程的⽚段([0, T])。
    返回所需⽚段的最⼩数⽬,如果⽆法完成该任务,则返回 -1。
    leetcode:1024. 视频拼接
    input:clips = [[0,2],[4,6],[8,10],[1,9],[1,5],[5,9]], T = 10
    output:3
    思路:
        1.首先对起点先升序排序,然后对终点降序排序
        2.由于cur_end初始值是0,所以第二个whilw在第一次遍历的时候,会自动跳出,从而res++
        3.
    '''
    if T == 0:
        return 0
    # 首先对起点先升序排序,然后对终点降序排序
    clips = sorted(clips, key=lambda x: (x[0], -x[1]))

    res, cur_end, next_end = 0, 0, 0
    i = 0
    # 由于cur_end初始值是0,所以第二个whilw在第一次遍历的时候,会自动跳出,从而res++
    while i < len(clips) and clips[i][0] <= cur_end:
        while i < len(clips) and clips[i][0] <= cur_end:
            # 迭代更新next_end和i
            # cur_end是上一轮迭代的,作用于第二个while
            next_end = max(next_end, clips[i][1])
            i += 1
        res += 1
        # 此时cur_end已变化,作用于第一个while
        cur_end = next_end
        if cur_end >= T:
            return res

    return -1

【测试例】

if __name__ == "__main__":
    merge([[1, 3], [2, 6], [8, 10], [15, 18]])
    print(eraseOverlapIntervals([[1, 2], [2, 3], [3, 4], [1, 3]]))
    print(videoStitching([[0, 2], [4, 6], [8, 10], [1, 9], [1, 5], [5, 9]], 10))

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值