LeetCode 题解工作台

统计区间中的整数数目

给你区间的 空 集,请你设计并实现满足要求的数据结构: 新增: 添加一个区间到这个区间集合中。 统计: 计算出现在 至少一个 区间中的整数个数。 实现 CountIntervals 类: CountIntervals() 使用区间的空集初始化对象 void add(int left, int rig…

category

3

题型

code_blocks

5

代码语言

hub

3

相关题

当前训练重点

困难 · design·结合·segment·树

bolt

答案摘要

根据题目描述,我们需要维护一个区间集合,支持区间的添加和查询操作。对于区间的添加,我们可以使用线段树来维护区间集合。 线段树将整个区间分割为多个不连续的子区间,子区间的数量不超过 。更新某个元素的值,只需要更新 个区间,并且这些区间都包含在一个包含该元素的大区间内。区间修改时,需要使用懒标记保证效率。

Interview AiBox logo

Interview AiBox 实时 AI 助手,陪你讲清 design·结合·segment·树 题型思路

试试 AiBox 面试助手arrow_forward
description

题目描述

给你区间的 集,请你设计并实现满足要求的数据结构:

  • 新增:添加一个区间到这个区间集合中。
  • 统计:计算出现在 至少一个 区间中的整数个数。

实现 CountIntervals 类:

  • CountIntervals() 使用区间的空集初始化对象
  • void add(int left, int right) 添加区间 [left, right] 到区间集合之中。
  • int count() 返回出现在 至少一个 区间中的整数个数。

注意:区间 [left, right] 表示满足 left <= x <= right 的所有整数 x

 

示例 1:

输入
["CountIntervals", "add", "add", "count", "add", "count"]
[[], [2, 3], [7, 10], [], [5, 8], []]
输出
[null, null, null, 6, null, 8]

解释
CountIntervals countIntervals = new CountIntervals(); // 用一个区间空集初始化对象
countIntervals.add(2, 3);  // 将 [2, 3] 添加到区间集合中
countIntervals.add(7, 10); // 将 [7, 10] 添加到区间集合中
countIntervals.count();    // 返回 6
                           // 整数 2 和 3 出现在区间 [2, 3] 中
                           // 整数 7、8、9、10 出现在区间 [7, 10] 中
countIntervals.add(5, 8);  // 将 [5, 8] 添加到区间集合中
countIntervals.count();    // 返回 8
                           // 整数 2 和 3 出现在区间 [2, 3] 中
                           // 整数 5 和 6 出现在区间 [5, 8] 中
                           // 整数 7 和 8 出现在区间 [5, 8] 和区间 [7, 10] 中
                           // 整数 9 和 10 出现在区间 [7, 10] 中

 

提示:

  • 1 <= left <= right <= 109
  • 最多调用  addcount 方法 总计 105
  • 调用 count 方法至少一次
lightbulb

解题思路

方法一:线段树(动态开点)

根据题目描述,我们需要维护一个区间集合,支持区间的添加和查询操作。对于区间的添加,我们可以使用线段树来维护区间集合。

线段树将整个区间分割为多个不连续的子区间,子区间的数量不超过 log(width)\log(width)。更新某个元素的值,只需要更新 log(width)\log(width) 个区间,并且这些区间都包含在一个包含该元素的大区间内。区间修改时,需要使用懒标记保证效率。

  • 线段树的每个节点代表一个区间;
  • 线段树具有唯一的根节点,代表的区间是整个统计范围,如 [1,N][1,N]
  • 线段树的每个叶子节点代表一个长度为 11 的元区间 [x,x][x,x]
  • 对于每个内部节点 [l,r][l,r],它的左儿子是 [l,mid][l,mid],右儿子是 [mid+1,r][mid+1,r], 其中 mid=(l+r)/2mid=⌊(l+r)/2⌋ (即向下取整)。

由于题目数据范围较大,我们可以使用动态开点的线段树来实现。动态开点的线段树是指,我们只在需要的时候才开点,而不是一开始就开好所有的点,这样可以节省空间。

时间复杂度方面,每次操作的时间复杂度为 O(logn)O(\log n)。空间复杂度为 O(m×logn)O(m \times \log n)。其中 mm 为操作次数,而 nn 为数据范围。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
class Node:
    __slots__ = ("left", "right", "l", "r", "mid", "v", "add")

    def __init__(self, l, r):
        self.left = None
        self.right = None
        self.l = l
        self.r = r
        self.mid = (l + r) // 2
        self.v = 0
        self.add = 0


class SegmentTree:
    def __init__(self):
        self.root = Node(1, int(1e9) + 1)

    def modify(self, l, r, v, node=None):
        if node is None:
            node = self.root
        if l > r:
            return
        if node.l >= l and node.r <= r:
            node.v = node.r - node.l + 1
            node.add = v
            return
        self.pushdown(node)
        if l <= node.mid:
            self.modify(l, r, v, node.left)
        if r > node.mid:
            self.modify(l, r, v, node.right)
        self.pushup(node)

    def query(self, l, r, node=None):
        if node is None:
            node = self.root
        if l > r:
            return 0
        if node.l >= l and node.r <= r:
            return node.v
        self.pushdown(node)
        v = 0
        if l <= node.mid:
            v += self.query(l, r, node.left)
        if r > node.mid:
            v += self.query(l, r, node.right)
        return v

    def pushup(self, node):
        node.v = node.left.v + node.right.v

    def pushdown(self, node):
        if node.left is None:
            node.left = Node(node.l, node.mid)
        if node.right is None:
            node.right = Node(node.mid + 1, node.r)
        if node.add != 0:
            left, right = node.left, node.right
            left.add = node.add
            right.add = node.add
            left.v = left.r - left.l + 1
            right.v = right.r - right.l + 1
            node.add = 0


class CountIntervals:
    def __init__(self):
        self.tree = SegmentTree()

    def add(self, left, right):
        self.tree.modify(left, right, 1)

    def count(self):
        return self.tree.query(1, int(1e9))


# Your CountIntervals object will be instantiated and called as such:
# obj = CountIntervals()
# obj.add(left, right)
# param_2 = obj.count()
speed

复杂度分析

指标
时间Depends on the final approach
空间Depends on the final approach
psychology

面试官常问的追问

外企场景
  • question_mark

    Look for familiarity with segment trees and ordered sets as applicable data structures for range queries and dynamic intervals.

  • question_mark

    Assess the candidate's ability to optimize the data structure operations, especially handling overlapping intervals.

  • question_mark

    Evaluate how well the candidate manages the trade-offs between time complexity and space efficiency in the proposed solution.

warning

常见陷阱

外企场景
  • error

    Failing to handle overlapping intervals correctly, leading to incorrect count results.

  • error

    Inefficient data structure choices, resulting in excessive space or time complexity for the given constraints.

  • error

    Not optimizing for the count operation, which can result in unnecessary recomputations or slow queries.

swap_horiz

进阶变体

外企场景
  • arrow_right_alt

    Implementing a dynamic range query system where intervals can be modified after addition.

  • arrow_right_alt

    Using a balanced binary search tree (BST) or other tree structures to handle dynamic interval additions and queries.

  • arrow_right_alt

    Expanding the problem to support removal of intervals, adding a new layer of complexity to the solution.

help

常见问题

外企场景

统计区间中的整数数目题解:design·结合·segment·树 | LeetCode #2276 困难