十二、leetcode刷题之【扫描线】

[TOC]

56. 合并区间(中等)

以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。

输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].

输入:intervals = [[1,4],[4,5]]
输出:[[1,5]]
解释:区间 [1,4] 和 [4,5] 可被视为重叠区间。
// ================ 解法一 ====================================
func merge(intervals [][]int) [][]int {
    //先从小到大排序
    sort.Slice(intervals, func(i, j int) bool {
        return intervals[i][0] < intervals[j][0]
    })
    //再弄重复的
    for i := 0; i < len(intervals)-1; i++ {
        // 当前右边界intervals[i][1]和下一区间左边界intervals[i+1][0]比较
        if intervals[i][1] >= intervals[i+1][0] {
            // 更新区间右边界最大值
            intervals[i][1] = max(intervals[i][1], intervals[i+1][1]) //赋值最大值
            // 删除下一个区间元素,因为此时当前区间右边界已经变了
            intervals = append(intervals[:i+1], intervals[i+2:]...) // 很重要,删除i+1这一行
            i--
        }
    }
    return intervals
}

func max(a, b int) int {
    if a > b {
        return a
    }
    return b
}

//  ====================  解法二(不易理解,建议还是用解法一)  ============================
func merge(intervals [][]int) [][]int {
    sort.Slice(intervals, func(i, j int) bool {
        return intervals[i][0] < intervals[j][0]
    })
    var res [][]int
    start, end := intervals[0][0], intervals[0][1]
    for i := 1; i < len(intervals); i++ {
        if intervals[i][0] <= end {
            if intervals[i][1] > end {
                end = intervals[i][1]
            }
        } else {
            res = append(res, []int{start, end})
            start, end = intervals[i][0], intervals[i][1]
        }
    }
    return append(res, []int{start, end})
}

57. 插入区间(中等)

给你一个 无重叠的 ,按照区间起始端点排序的区间列表。在列表中插入一个新的区间,你需要确保列表中的区间仍然有序且不重叠(如果有必要的话,可以合并区间)。

输入:intervals = [[1,3],[6,9]], newInterval = [2,5]
输出:[[1,5],[6,9]]

输入:intervals = [[1,2],[3,5],[6,7],[8,10],[12,16]], newInterval = [4,8]
输出:[[1,2],[3,10],[12,16]]
解释:这是因为新的区间 [4,8] 与 [3,5],[6,7],[8,10] 重叠。

输入:intervals = [], newInterval = [5,7]
输出:[[5,7]]
func insert(intervals [][]int, newInterval []int) [][]int {
    intervals = append(intervals, newInterval)
    sort.Slice(intervals, func(i, j int) bool {
        return intervals[i][0] < intervals[j][0]
    })
    //再弄重复的
    for i := 0; i < len(intervals)-1; i++ {
        // 当前右边界intervals[i][1]和下一区间左边界intervals[i+1][0]比较
        if intervals[i][1] >= intervals[i+1][0] {
            // 更新区间右边界最大值
            intervals[i][1] = max(intervals[i][1], intervals[i+1][1]) //赋值最大值
            // 删除下一个区间元素,因为此时当前区间右边界已经变了
            intervals = append(intervals[:i+1], intervals[i+2:]...) // 很重要,删除i+1这一行
            i--
        }
    }
    return intervals
}

163. 缺失的区间(会员/简单)

给定一个排序的整数数组 nums ,其中元素的范围在 闭区间 [lower, upper] 当中,返回不包含在数组中的缺失区间。

输入: nums = [0, 1, 3, 50, 75], lower = 0 和 upper = 99,
输出: ["2", "4->49", "51->74", "76->99"]

func main() {
    nums := []int{0, 1, 3, 50, 75}
    lower := 0
    upper := 99
    fmt.Println(findMissingRanges(nums, lower, upper))
}

func CreateResult(start, end int) (str string) {
    if start == end {
        //return string(start)
        str = fmt.Sprintf("%d", start)
    } else {
        //return string(start)+"->"+string(end)
        str = fmt.Sprintf("%d->%d", start, end)
    }

    return str
}

func CreateParts(newNums []int, lower int, upper int) []string {
    if len(newNums) == 0 {
        return []string{CreateResult(lower, upper)}
    }
    result := make([]string, 0)
    // 这里是左边界
    if lower < newNums[0] {
        result = append(result, CreateResult(lower, newNums[0]-1))
    }
    //这里是主体,注意这里长度是-1
    for i := 0; i < len(newNums)-1; i++ {
        if newNums[i+1]-1 <= newNums[i] {
            continue
        }
        result = append(result, CreateResult(newNums[i]+1, newNums[i+1]-1))
    }
    //这里是右边界
    if upper > newNums[len(newNums)-1] {
        result = append(result, CreateResult(newNums[len(newNums)-1]+1, upper))
    }

    return result
}

func findMissingRanges(nums []int, lower int, upper int) []string {
    if len(nums) == 0 {
        return []string{CreateResult(lower, upper)}
    }
    newNums := make([]int, 0)
    for i := 0; i < len(nums); i++ {
        if nums[i] < lower || nums[i] > upper {
            continue
        }
        newNums = append(newNums, nums[i])
    }
    return CreateParts(newNums, lower, upper)
}

1272. 删除区间(会员/中等)

实数集合可以表示为若干不相交区间的并集,其中每个区间的形式为 [a, b)(左闭右开),表示满足 a <= x < b 的所有实数  x 的集合。如果某个区间 [a, b) 中包含实数 x ,则称实数 x 在集合中。

给你一个 有序的 不相交区间列表 intervals 。intervals 表示一个实数集合,其中每一项 intervals[i] = [ai, bi] 都表示一个区间 [ai, bi) 。再给你一个要删除的区间 toBeRemoved 。

返回 一组实数,该实数表示intervals 中 删除 了 toBeRemoved 的部分 。换句话说,返回实数集合,并满足集合中的每个实数 x 都在 intervals 中,但不在 toBeRemoved 中。你的答案应该是一个如上所述的 有序的 不相连的间隔列表 。

输入:intervals = [[0,2],[3,4],[5,7]], toBeRemoved = [1,6]
输出:[[0,1],[6,7]]

输入:intervals = [[0,5]], toBeRemoved = [2,3]
输出:[[0,2],[3,5]]

输入:intervals = [[-5,-4],[-3,-2],[1,2],[3,5],[8,9]], toBeRemoved = [-1,4]
输出:[[-5,-4],[-3,-2],[4,5],[8,9]]

func main() {
    intervals := [][]int{
        {0, 2},
        {3, 4},
        {5, 7},
    }
    toBeRemoved := []int{1, 6}
    result := removeInterval(intervals, toBeRemoved)
    fmt.Println(result)
}

func removeInterval(intervals [][]int, toBeRemoved []int) [][]int {
    //先从小到大排序
    sort.Slice(intervals, func(i, j int) bool {
        return intervals[i][0] < intervals[j][0]
    })

    var res [][]int
    for i := 0; i < len(intervals); i++ {
        start, end := intervals[i][0], intervals[i][1]
        if start >= toBeRemoved[1] || end <= toBeRemoved[0] {
            res = append(res, []int{start, end})
        } else {
            if start < toBeRemoved[0] {
                res = append(res, []int{start, toBeRemoved[0]})
            }

            if end > toBeRemoved[1] {
                res = append(res, []int{toBeRemoved[1], end})
            }
        }
    }
    return res
}

252. 会议室(会员/简单)

给定一个会议时间安排的数组 intervals ,每个会议时间都会包括开始和结束的时间 intervals[i] = [starti, endi] ,请你判断一个人是否能够参加这里面的全部会议。

输入:intervals = [[0,30],[5,10],[15,20]]
输出:false

输入:intervals = [[7,10],[2,4]]
输出:true
func main() {
    intervals := [][]int{
        {0, 30},
        {5, 10},
        {15, 20},
    }

    result := canAttendMeetings(intervals)
    fmt.Println(result)
}

func canAttendMeetings(intervals [][]int) bool {
    sort.Slice(intervals, func(i, j int) bool {
        return intervals[i][0] < intervals[j][0]
    })

    fmt.Println(intervals)

    for i := 0; i < len(intervals)-1; i++ {
        if intervals[i][1] > intervals[i+1][0] {
            return false
        }
    }
    return true
}

253. 会议室 II(会员/中等)

给你一个会议时间安排的数组 intervals ,每个会议时间都会包括开始和结束的时间 intervals[i] = [starti, endi] ,返回 所需会议室的最小数量 。

输入:intervals = [[0,30],[5,10],[15,20]]
输出:2

输入:intervals = [[7,10],[2,4]]
输出:1
/*//  ====================== 解法一:数飞机解法 =======================
// 其实这种做法就是数飞机法,这里是全都乘以10,然后加了标记1或者2的。
本题可以先假设只有一个大大大会议室,参加不同会议的人都可以自由进出会议室,咱们只要看到同一时间会议室内最多有几批人,就知道需要几个会议了。

咱们的做法是:
1、先把进出时间从小到大进行排序。
这里有个小技巧,怎么样区分哪个是进,哪个是出呢?只要加个标志位就好了。【这里其实就是数飞机的思路了】
为了不影响原数大小,所有数值*10后,再加上标志位,进为2,出为1。
为什么进要大一点呢?因为如果A组人员刚出,B组就进来了,那么可以认为一个会议室是可以容纳这两组人开会的。所以,咱们先出后进,防止多算。
2、如果有人进房间,那么需要的会议室数量curNeedRoom就+1,反之,有人出房间,curNeedRoom就-1。
3、记录下curNeedRoom的最大值,就是咱们需要的会议室
*/
func minMeetingRooms(intervals [][]int) int {
    var nums []int
    for _, v := range intervals {
        nums = append(nums, v[0]*10+2) // 进为2
        nums = append(nums, v[1]*10+1) // 出为1,排序要从小到大,先出后进。
    }
    sort.Ints(nums)
    maxRoom := 0
    curNeedRoom := 0
    for _, v := range nums {
        if v%10 == 1 {
            curNeedRoom--
        } else {
            curNeedRoom++
        }
        if curNeedRoom > maxRoom {
            maxRoom = curNeedRoom
        }
    }
    return maxRoom
}


/*
================== 解法二 =======================
下面的整体思路是:
第一步:拆分为两个数组,一个start,一个end
第二步:将两个数组分别排序
第三步:遍历单数组,如果开始时间<结束时间,room++,否则endItr增加
*/
func minMeetingRooms(intervals [][]int) int {
    n := len(intervals)
    begin, end := make([]int, n), make([]int, n)
    for i := 0; i < n; i++ {
        begin[i] = intervals[i][0]
        end[i] = intervals[i][1]
    }
    sort.Ints(begin) //递增的开始时间数组
    sort.Ints(end)   //递增的结束时间数组

    endItr, cnt := 0, 0
    for i := 0; i < n; i++ { // 只要把startTime从头到位弄完就行
        if begin[i] < end[endItr] { // 如果开始时间小于最近的结束时间,则会议室数量加1,接着到第二个starttime,如果还小于endTime,肯定还要一个房间
            cnt++
        } else {
            endItr++
        }
    }
    return cnt
}

// =========== 解法三:差分 =============
func minMeetingRooms(intervals [][]int) int {
    nums := make([]int, 1000001) // 这里是时间 0 <= starti < endi <= 106
    var df Difference
    df.init(nums)
    for _, interval := range intervals {
        df.increment(interval[0], interval[1]-1, 1) // interval[0]时刻使用, interval[1]-1用完
    }
    res := df.result()
    return maxValInSlice(res)
}

func maxValInSlice(input []int) int {
    maxVal := input[0]
    for i := range input {
        if input[i] > maxVal {
            maxVal = input[i]
        }
    }
    return maxVal
}

435. 无重叠区间(中等)

给定一个区间的集合 intervals ,其中 intervals[i] = [starti, endi] 。返回 需要移除区间的最小数量,使剩余区间互不重叠 。

输入: intervals = [[1,2],[2,3],[3,4],[1,3]]
输出: 1
解释: 移除 [1,3] 后,剩下的区间没有重叠。

输入: intervals = [ [1,2], [1,2], [1,2] ]
输出: 2
解释: 你需要移除两个 [1,2] 来使剩下的区间没有重叠。

输入: intervals = [ [1,2], [2,3] ]
输出: 0
解释: 你不需要移除任何区间,因为它们已经是无重叠的了
func eraseOverlapIntervals(intervals [][]int) int {
    sort.Slice(intervals, func(i, j int) bool {
        return intervals[i][1] < intervals[j][1]
    })

    fmt.Println(intervals)

    count := 1
    end := intervals[0][1]
    for i := 1; i < len(intervals); i++ {
        if intervals[i][0] >= end { // /当区间的左边界大于上一个区间的右边界的时候 说明是一对不重合区间
            count++
            end = intervals[i][1]
        }
    }
    return len(intervals) - count // //intervals的长度减去最多的不重复的区间 就是最少删除区间的个数
}

1288. 删除被覆盖区间(中等)

给你一个区间列表,请你删除列表中被其他区间所覆盖的区间。只有当 c <= a 且 b <= d 时,我们才认为区间 [a,b) 被区间 [c,d) 覆盖。
在完成所有删除操作后,请你返回列表中剩余区间的数目。

输入:intervals = [[1,4],[3,6],[2,8]]
输出:2
解释:区间 [3,6] 被区间 [2,8] 覆盖,所以它被删除了。

352. 将数据流变为多个不相交区间(困难)

1229. 安排会议日程(会员/中等/务必做一下)

给定两个人的空闲时间表:slots1 和 slots2,以及会议的预计持续时间 duration,请你为他们安排 时间段最早 且合适的会议时间。如果没有满足要求的会议时间,就请返回一个 空数组。「空闲时间」的格式是 [start, end],由开始时间 start 和结束时间 end 组成,表示从 start 开始,到 end 结束。 

题目保证数据有效:同一个人的空闲时间不会出现交叠的情况,也就是说,对于同一个人的两个空闲时间 [start1, end1] 和 [start2, end2],要么 start1 > end2,要么 start2 > end1。

输入:slots1 = [[10,50],[60,120],[140,210]], slots2 = [[0,15],[60,70]], duration = 8
输出:[60,68]

输入:slots1 = [[10,50],[60,120],[140,210]], slots2 = [[0,15],[60,70]], duration = 12
输出:[]

986. 区间列表的交集(中等)

给定两个由一些 闭区间 组成的列表,firstList 和 secondList ,其中 firstList[i] = [starti, endi] 而 secondList[j] = [startj, endj] 。每个区间列表都是成对 不相交 的,并且 已经排序 。返回这 两个区间列表的交集 。形式上,闭区间 [a, b](其中 a <= b)表示实数 x 的集合,而 a <= x <= b 。两个闭区间的 交集 是一组实数,要么为空集,要么为闭区间。例如,[1, 3] 和 [2, 4] 的交集为 [2, 3] 。

输入:firstList = [[0,2],[5,10],[13,23],[24,25]], secondList = [[1,5],[8,12],[15,24],[25,26]]
输出:[[1,2],[5,5],[8,10],[15,23],[24,24],[25,25]]

输入:firstList = [[1,3],[5,9]], secondList = []
输出:[]

输入:firstList = [], secondList = [[4,8],[10,12]]
输出:[]

输入:firstList = [[1,7]], secondList = [[3,10]]
输出:[[3,7]]

759. 员工空闲时间(会员/困难/务必做一下)

给定员工的 schedule 列表,表示每个员工的工作时间。每个员工都有一个非重叠的时间段  Intervals 列表,这些时间段已经排好序。
返回表示 所有 员工的 共同,正数长度的空闲时间 的有限时间段的列表,同样需要排好序。

输入:schedule = [[[1,2],[5,6]],[[1,3]],[[4,10]]]
输出:[[3,4]]
解释:
共有 3 个员工,并且所有共同的
空间时间段是 [-inf, 1], [3, 4], [10, inf]。
我们去除所有包含 inf 的时间段,因为它们不是有限的时间段。

输入:schedule = [[[1,3],[6,7]],[[2,4]],[[2,5],[9,12]]]
输出:[[5,6],[7,9]]

218. 天际线问题(困难)

城市的 天际线 是从远处观看该城市中所有建筑物形成的轮廓的外部轮廓。给你所有建筑物的位置和高度,请返回 由这些建筑物形成的 天际线 。每个建筑物的几何信息由数组 buildings 表示,其中三元组 buildings[i] = [lefti, righti, heighti] 表示:
lefti 是第 i 座建筑物左边缘的 x 坐标。
righti 是第 i 座建筑物右边缘的 x 坐标。
heighti 是第 i 座建筑物的高度。
你可以假设所有的建筑都是完美的长方形,在高度为 0 的绝对平坦的表面上。

天际线 应该表示为由 “关键点” 组成的列表,格式 [[x1,y1],[x2,y2],...] ,并按 x 坐标 进行 排序 。关键点是水平线段的左端点。列表中最后一个点是最右侧建筑物的终点,y 坐标始终为 0 ,仅用于标记天际线的终点。此外,任何两个相邻建筑物之间的地面都应被视为天际线轮廓的一部分。

注意:输出天际线中不得有连续的相同高度的水平线。例如 [...[2 3], [4 5], [7 5], [11 5], [12 7]...] 是不正确的答案;三条高度为 5 的线应该在最终输出中合并为一个:[...[2 3], [4 5], [12 7], ...]

输入:buildings = [[2,9,10],[3,7,15],[5,12,12],[15,20,10],[19,24,8]]
输出:[[2,10],[3,15],[7,12],[12,0],[15,10],[20,8],[24,0]]
解释:
图 A 显示输入的所有建筑物的位置和高度,
图 B 显示由这些建筑物形成的天际线。图 B 中的红点表示输出列表中的关键点。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 210,914评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,935评论 2 383
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,531评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,309评论 1 282
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,381评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,730评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,882评论 3 404
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,643评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,095评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,448评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,566评论 1 339
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,253评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,829评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,715评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,945评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,248评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,440评论 2 348

推荐阅读更多精彩内容