Leetcode 区间

区间

  • 56. 合并区间
  • 57. 插入区间
    • 方法一:插入,直接利用 56 题合并
    • 方法二:
  • 163. 缺失的区间
  • 228. 汇总区间
  • 252. 会议室 (判断区间是否重叠)
  • 327. 区间和的个数
  • 352. 将数据流变为多个不相交区间
  • 370. 区间加法
  • 436. 寻找右区间
    • 方法一:list
    • 方法二:list dict
  • 435. 无重叠区间
    • 方法一:动态规划
  • 632. 最小区间
  • 763. 划分字母区间
  • 795. 区间子数组个数
    • 方法一:动态规划
    • 方法一:计数
  • 986. 区间列表的交集
  • 1272. 删除区间
  • 1285. 找到连续区间的开始和结束数字
  • 1288. 删除被覆盖区间
    • 方法一:贪心算法
    • 方法二:枚举
  • 1508. 子数组和排序后的区间和
  • 1523. 在区间范围内统计奇数数目
  • 1787. 使所有区间的异或结果为零
  • 1851. 包含每个查询的最小区间
    • 方法一:超时
    • 方法二:离线算法
  • 1893. 检查是否区域内所有整数都被覆盖
  • 1893. 检查是否区域内所有整数都被覆盖
    • 方法一:遍历
    • 方法二:双指针
  • 56. 合并区间
    • 方法一:
    • 上题方法三:

56. 合并区间

Leetcode
首先对区间按照起始端点进行升序排序,然后逐个判断当前区间是否与前一个区间重叠,如果不重叠的话将当前区间直接加入结果集,反之如果重叠的话,就将当前区间与前一个区间进行合并。

class Solution:
    def merge(self, intervals: List[List[int]]) -> List[List[int]]:        
        intervals.sort()    
        # left, right = intervals[0]
        # res = []
        # for s, e in intervals[1:]:
        #     if s > right:
        #         res.append([left, right])      
        #         left = s

        #     right = max(right, e)

        # res.append([left, right]) 
        
        # return res


        merged = []
        for interval in intervals:
            # 如果列表为空,或者当前区间与上一区间不重合,直接添加
            if not merged or merged[-1][1] < interval[0]:
                merged.append(interval)
            else:
                # 否则的话,与上一区间合并
                merged[-1][1] = max(merged[-1][1], interval[1])

        return merged

57. 插入区间

Leetcode

方法一:插入,直接利用 56 题合并

class Solution:
    def insert(self, intervals: List[List[int]], newInterval: List[int]) -> List[List[int]]:        
        # intervals.append(newInterval)
        # intervals.sort()
        bisect.insort(intervals, newInterval)

        res = []
        for interval in intervals:
            # interval 在 res[-1] 的右侧
            if not res or res[-1][1] < interval[0]:
                res.append(interval)
            else:
                # 扩展 res[-1] 的右边界
                res[-1][1] = max(res[-1][1], interval[1])
        
        return res

方法二:

直接遍历区间列表,寻找新区间的插入位置:
如果当前区间的结束位置小于新区间的开始位置,说明当前区间在新区间的左边且相离,将新区间左边且相离的区间加入结果集;

接着判断当前区间是否与新区间重叠,重叠的话就进行合并,直到遍历到当前区间在新区间的右边且相离,将最终合并后的新区间加入结果集;

最后将新区间右边且相离的区间加入结果集。

class Solution:
    def insert(self, intervals: List[List[int]], newInterval: List[int]) -> List[List[int]]:
        left, right = newInterval
        placed = False
        ans = list()
        for x, y in intervals:
            if x > right:  # 在插入区间的右侧且无交集
                if not placed:
                    ans.append([left, right])
                    placed = True
                ans.append([x, y])
            elif y < left: # 在插入区间的左侧且无交集                
                ans.append([x, y])
            else:
                # 与插入区间有交集,计算它们的并集
                left = min(left, x)
                right = max(right, y)
        
        if not placed: ans.append([left, right]) # newInterval 插入最右侧
        return ans

163. 缺失的区间

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

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

228. 汇总区间

Leetcode

class Solution:
    def summaryRanges(self, nums: List[int]) -> List[str]:
        n, res, left = len(nums), [], 0
        for i in range(1, n + 1): # 可以添加哨兵
            if i == n or nums[i] - nums[i - 1] != 1:
                if left == i - 1:
                    res.append(str(nums[i - 1]))
                else:
                    res.append(f'{nums[left]}->{nums[i - 1]}')
                left = i

        return res

252. 会议室 (判断区间是否重叠)

难度:Easy

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

示例 1::
输入: intervals = [[0,30],[5,10],[15,20]]
输出: false
解释: 存在重叠区间,一个人在同一时刻只能参加一个会议。

示例 2::
输入: intervals = [[7,10],[2,4]]
输出: true
解释: 不存在重叠区间。

因为一个人在同一时刻只能参加一个会议,因此题目实质是判断是否存在重叠区间,将区间按照会议开始时间进行排序,然后遍历一遍判断即可。

def canAttendMeetings(intervals):
        # 将区间按照会议开始实现升序排序
        intervals.sort()
        # 遍历会议,如果下一个会议在前一个会议结束之前就开始了,返回 false。
        for i in range(1, len(intervals)): 
            if intervals[i][0] < intervals[i - 1][1]:
                return False
            
        return True

intervals = [[0,30],[5,10],[15,20]]
intervals = [[7,10],[2,4]]
canAttendMeetings(intervals)

327. 区间和的个数

Leetcode

352. 将数据流变为多个不相交区间

Leetcode

class SummaryRanges:    
    def __init__(self):
        self.x = [-2, 10002] # 两哨兵

    def addNum(self, val: int) -> None:
        x = self.x
        i = bisect.bisect_left(x, val)
        # [-2,1,2,3,6,8,10002] => [-2,1,3,6,6,8,8,10002] 
        # 奇遇为区间 [[1,3],[6,6],[8,8]]
        if i % 2: # i 为偶数正好在区间内,处理奇数即可。
            if val == x[i-1] + 1: x[i-1] = val # 7 合在前一个区间 
            if val == x[i] - 1: x[i] = val # 7 合在后一个区间 
            if x[i-1] == x[i]:      # 合并后正好可以连接在一起
                x[i-2:i+2] = [x[i-2], x[i+1]]

            if x[i-1] + 1 < val < x[i] - 1: # 孤点插双
                x.insert(i, val)
                x.insert(i+1,val)

    def getIntervals(self) -> List[List[int]]:
        x = self.x[1:-1]
        return [[x[i-1],x[i]] for i in range(1,len(x),2)]

保留输入流

class SummaryRanges:    
    def __init__(self):        
        # self.x = []
        self.x = set()

    def addNum(self, val: int) -> None:
        # self.x.append(val)
        self.x.add(val)

    def getIntervals(self) -> List[List[int]]:
        # x = sorted(set(self.x))
        x = sorted(self.x)        
        n = len(x)

        if not x: return []
        res = [[x[0], x[0]]]
        for i in range(1, n):
            if x[i] == x[i - 1] + 1: # 连续更新右边界
                res[-1][1] = x[i]
            else:
                res.append([x[i], x[i]])                

        return res

370. 区间加法

436. 寻找右区间

Leetcode

方法一:list

class Solution:
    def findRightInterval(self, intervals: List[List[int]]) -> List[int]:
        d = [[x[0], i] for i, x in enumerate(intervals)]       
        d.sort() # 先保存索引,后排序。
        res = []
        n = len(intervals)
        for x in intervals:
            idx = bisect.bisect_left(d, [x[1]])
            
            if idx == n: res.append(-1)
            else:res.append(d[idx][1])

        return res

方法二:list dict

class Solution:
    def findRightInterval(self, intervals: List[List[int]]) -> List[int]:
        d = {x[0]:i for i, x in enumerate(intervals)}
        w = [x[0] for x in intervals]
        w.sort()
        res = []
        n = len(intervals)
        for x in intervals:
            idx = bisect.bisect_left(w, x[1])            
            if idx == n: res.append(-1)
            else:res.append(d[w[idx]])
            
        return res

435. 无重叠区间

Leetcode

方法一:动态规划

class Solution:
    def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:
        intervals.sort(key=lambda x:x[0]) # 不用 key 全部通过测试用例 但会超时
        n = len(intervals)
        if n == 0: return 0
        dp = [1] * n #初始化
        
        for i in range(n):
            for j in range(i-1,-1,-1): 
                if intervals[j][1] <= intervals[i][0]:
                    dp[i] = max(dp[j] + 1,dp[i])
                    break
                    
        return n - max(dp)

632. 最小区间

Leetcode

763. 划分字母区间

Leetcode

class Solution:
    def partitionLabels(self, s: str) -> List[int]:
        left, right, res, n = 0, 1, [], len(s) 

        last = defaultdict(int) # dict 需要记录 字母的最大索引
        for i in range(n):
            last[s[i]] = i

        for i in range(n):
            if i == right:
                res.append(right - left)
                left = right 
            # idx = s.rfind(s[i])  # rfind 从右边找相同字母的最大索引
            # right = max(idx + 1, right)

            right = max(last[s[i]] + 1, right)

        res.append(n - left)

        return res

795. 区间子数组个数

Leetcode

方法一:动态规划

dp[i] 表示以 A[i] 结尾的符合条件的子数组数量。

A[i] > R: 子数组中不能包含大于 R 的数,dp[i] = 0;
A[i] < L: 小于 L 的数不能单独成为子数组,dp[i] = dp[i - 1];
A[i]∈[L,R]: 以 A[i] 结尾的子数组数量为 i - last,其中 last 为上一个大于 R 的数的位置。
最终答案为: ∑ i n d p [ i ] \sum_i^n{dp[i]} indp[i]

代码中的 lastCount 即为 dp[i - 1]。

class Solution:
    def numSubarrayBoundedMax(self, nums: List[int], left: int, right: int) -> int:
        last = lastCount = res = 0
        
        for i in range(len(nums)):
            if nums[i] > right:
                last, lastCount = i, 0
            elif nums[i] < left:
                res += lastCount
            else:
                lastCount = i - last
                res += lastCount

        return res

方法一:计数

连续、非空且其中最大元素满足大于等于L 小于等于R的子数组,即:< R,且至少包含一个 ≥ L 的子数组。

假设一个元素小于 L 标记为 0,位于 [L, R] 之间标记为 1,大于 R 标记为 2。
找出不包含 2 且至少包含一个 1 的子数组数量。可以看作是所有的 2 将数组拆分为仅包含 0 或 1 的子数组。

需要计算每个只包含 0 或 1 的数组中,至少包含一个 1 的子数组数量。可以转换为先找出所有的子数组,再从中减去只包含 0 的子数组。

986. 区间列表的交集

1272. 删除区间

1285. 找到连续区间的开始和结束数字

1288. 删除被覆盖区间

Leetcode

方法一:贪心算法

贪心算法的思想是在每一步都选取最优的方案,从而得到全局最优解。

class Solution:
    def removeCoveredIntervals(self, intervals: List[List[int]]) -> int:
        # Sort by start point.
        # If two intervals share the same start point
        # put the longer one to be the first.
        intervals.sort(key = lambda x: (x[0], -x[1]))
        # count = prev_end = 0        
        
        # for _, end in intervals:
        #     # if current interval is not covered
        #     # by the previous one
        #     if end > prev_end:
        #         count += 1    
        #         prev_end = end
        
        # return count

        ans, prev_end = len(intervals), intervals[0][1]
        for _, end in intervals[1:]:
            if end <= prev_end:
                ans -= 1
            else:
                prev_end = end
                
        return ans

方法二:枚举

class Solution:
    def removeCoveredIntervals(self, x: List[List[int]]) -> int:
        ans = n = len(xs)

        for i in range(n):
            for j in range(n):
                if i != j and x[j][0] <= x[i][0] and x[i][1] <= x[j][1]:
                    ans -= 1
                    break
        return ans

1508. 子数组和排序后的区间和

1523. 在区间范围内统计奇数数目

Leetcode
只要 low 和 high 中有奇数,就返回 (high - low) // 2 + 1;否则返回 (high - low) // 2 。

class Solution:
    def countOdds(self, low: int, high: int) -> int:
    
        return (high - low) // 2 + 1 if low % 2 or high % 2 else (high - low) // 2

1787. 使所有区间的异或结果为零

1851. 包含每个查询的最小区间

Leetcode

方法一:超时

class Solution:
    def minInterval(self, intervals: List[List[int]], queries: List[int]) -> List[int]:
        res = []
        intervals.sort(key=lambda x:(x[1] - x[0]))

        for i in queries:       
            for x, y in intervals:
                if i >= x and i <= y:               
                    res.append(y - x + 1)
                    break
                
            else:
                res.append(-1)

        return res

方法二:离线算法

离线化的思想是:将已有区间和查询值均按照从小到大的顺序排列后,再进行查询,确保比当前查询值小的区间已经在前面被访问过,无需重复查找,目的是降低时间复杂度。

  1. 将 intervals 和 queries 排序;
  2. 从小到大遍历 queries 中的元素 q,并将 intervals 中所有左端点小于等于 q 的区间长度和右端点放入堆中;
  3. 判断查询元素 q 是否在堆顶存储区间中,即判断区间的右端点是否大于等于 q,如果不在堆顶的区间中,则将堆顶元素弹出;
  4. 判断堆是否为空,如果不为空,则堆顶元素对应的区间长度即为 res 中查询索引i对应的结果。
class Solution:
    def minInterval(self, intervals: List[List[int]], queries: List[int]) -> List[int]: 
class Solution:
    def minInterval(self, intervals: List[List[int]], queries: List[int]) -> List[int]:      
        m, n = len(intervals), len(queries)
        res = [-1] * n
        heap = []
        # 将queries中数字按照从小到大的顺序排列,并记录每个值对应的下标
        qr = sorted([(i,q) for i,q in enumerate(queries)],key=lambda x:x[1])
        
        intervals.sort()
        index = 0
        for i, q in qr:
            # 如果 intervals 中区间的左端点小于等于查询值q,则将区间的长度和右端点加入堆中
            while index < m and intervals[index][0] <= q:
                l, r = intervals[index]
                heapq.heappush(heap, [r - l + 1, r])
                index += 1
            # 如果堆顶元素存储的右端点小于查询值,则对顶元素出堆
            while heap and heap[0][1] < q:
                heapq.heappop(heap)
            # 如果堆不为空,堆顶元素即为所求
            if heap:
                res[i] = heap[0][0]
                
        return res

1893. 检查是否区域内所有整数都被覆盖

Leetcode

class Solution:
    def isCovered(self, ranges: List[List[int]], left: int, right: int) -> bool:
        ## 方法一:检查第一个数是否存在区间覆盖 
        # for i in range(left, right + 1):
        #     for L, R in ranges:                
        #         if i in range(L, R + 1): # L <= i <= R
        #             break
        #     else:
        #         return False

        # return True

        ## 方法二:排序后双指针缩小范围
        # ranges.sort()
        # for L, R in ranges: 
        #     if L <= left : 
        #         left = max(left, R + 1) 
        #     else:
        #         return False
        #     if right <= R : 
        #         right = min(right, L - 1)

        # return right < left
        
        ## 方法三:排序后合并区间
        # ranges.sort()
        # merged = []
        # for interval in ranges:
        #     # 如果列表为空,或者当前区间与 merged 无法合并,更新 merged
        #     if not merged or merged[1] < interval[0] - 1: # - 1 
        #         merged = interval               
        #     else:
        #         # 否则的话,区间合并
        #         merged[1] = max(merged[1], interval[1])

        #     if merged[0] <= left and merged[1] >= right: return True
        #     if left < merged[0]:return False

        # return False

        ## 方法四: 差分数组 前缀和 
        diff = [0] * 52  
        for l, r in ranges: # 相当于 [l, r] 区间内都加一           
            diff[l] += 1 # 相对于前缀和来说,l - 1 以后全部加一
            diff[r+1] -= 1
        
        curr = 0 # 前缀和
        for i in range(1, 51):
            curr += diff[i]
            if left <= i <= right and curr <= 0: # 说明没有被覆盖
                return False
        return True

1893. 检查是否区域内所有整数都被覆盖

方法一:遍历

class Solution:
    def isCovered(self, ranges: List[List[int]], left: int, right: int) -> bool:
        for i in range(left,right+1):
            for L, R in ranges:
                #if L <= i <= R:
                if i in range(L,R+1):
                    break
            else:
                return False
        return True

方法二:双指针

class Solution:
    def isCovered(self, ranges: List[List[int]], left: int, right: int) -> bool:
        ranges.sort()         
        for L, R in ranges:
            if left >= L:
                # 满足条件的区间是连续的包含如:[3,4][4,5]
                left = max(left, R+1)
            
            if right <= R: 
                right = L-1 # L 排序 R 无序               

        return right < left

56. 合并区间

方法一:

class Solution:
    def merge(self, intervals: List[List[int]]) -> List[List[int]]:                   
        intervals.sort()        
        start, end = intervals[0]
        ret = [] 
        for L, R in intervals[1:]:
            if L > end: # L > end+1 [1,2] [3,4] => [1,4] 可连续但不重叠
                ret.append([start,end]) # 前一组不再扩展了,添加
                start = L
            end = max(end, R) # 条件成立 end = R 不成立 max(end, R),合并成一条。

        ret.append([start,end]) # 处理最后一组
        return ret

上题方法三:

class Solution:
    def isCovered(self, ranges: List[List[int]], left: int, right: int) -> bool:
        def merge(ranges):
            ranges.sort()   
            s, e = ranges[0]
            res = []
            for L, R in ranges[1:]:
                if L > e+1:
                    res.append([s, e])      
                    s = L
                e = max(e,R)

            res.append([s, e])             
            return res

        ranges = merge(ranges)

        for L, R in ranges:
            if L <= left and right <= R:
                return True
        else:
            return False         

你可能感兴趣的:(leetcode,算法)