|
| 1 | +### 题目描述 |
| 2 | + |
| 3 | +这是 LeetCode 上的 **[1109. 航班预订统计](https://leetcode-cn.com/problems/corporate-flight-bookings/solution/gong-shui-san-xie-yi-ti-shuang-jie-chai-fm1ef/)** ,难度为**中等**。 |
| 4 | + |
| 5 | +Tag : 「区间求和问题」、「差分」、「线段树」 |
| 6 | + |
| 7 | + |
| 8 | + |
| 9 | +这里有 `n` 个航班,它们分别从 `1` 到 `n` 进行编号。 |
| 10 | + |
| 11 | +有一份航班预订表 `bookings`,表中第 `i` 条预订记录 $bookings[i] = [first_i, last_i, seats_i]$ 意味着在从 $first_i$ 到 $last_i$ (包含 $first_i$ 和 $last_i$ )的 每个航班 上预订了 $seats_i$ 个座位。 |
| 12 | + |
| 13 | +请你返回一个长度为 `n` 的数组 `answer`,其中 `answer[i]` 是航班 `i` 上预订的座位总数。 |
| 14 | + |
| 15 | +示例 1: |
| 16 | +``` |
| 17 | +输入:bookings = [[1,2,10],[2,3,20],[2,5,25]], n = 5 |
| 18 | + |
| 19 | +输出:[10,55,45,25,25] |
| 20 | + |
| 21 | +解释: |
| 22 | +航班编号 1 2 3 4 5 |
| 23 | +预订记录 1 : 10 10 |
| 24 | +预订记录 2 : 20 20 |
| 25 | +预订记录 3 : 25 25 25 25 |
| 26 | +总座位数: 10 55 45 25 25 |
| 27 | +因此,answer = [10,55,45,25,25] |
| 28 | +``` |
| 29 | +示例 2: |
| 30 | +``` |
| 31 | +输入:bookings = [[1,2,10],[2,2,15]], n = 2 |
| 32 | + |
| 33 | +输出:[10,25] |
| 34 | + |
| 35 | +解释: |
| 36 | +航班编号 1 2 |
| 37 | +预订记录 1 : 10 10 |
| 38 | +预订记录 2 : 15 |
| 39 | +总座位数: 10 25 |
| 40 | +因此,answer = [10,25] |
| 41 | +``` |
| 42 | +提示: |
| 43 | +* 1 <= n <= 2ドル * 10^4$ |
| 44 | +* 1 <= bookings.length <= 2ドル * 10^4$ |
| 45 | +* bookings[i].length == 3 |
| 46 | +* 1 <= firsti <= lasti <= n |
| 47 | +* 1 <= seatsi <= 10ドル^4$ |
| 48 | + |
| 49 | +--- |
| 50 | + |
| 51 | +### 基本分析 |
| 52 | + |
| 53 | +本题只涉及「区间修改 + 单点查询」,属于「区间求和」问题中的入门难度。 |
| 54 | + |
| 55 | +对于各类「区间求和」问题,该用什么方式进行求解,之前在 [这里](https://leetcode-cn.com/problems/range-sum-query-mutable/solution/guan-yu-ge-lei-qu-jian-he-wen-ti-ru-he-x-41hv/) 提到过。 |
| 56 | + |
| 57 | +此处可以再总结一下(加粗字体为最佳方案): |
| 58 | + |
| 59 | +* 数组不变,区间查询:**前缀和**、树状数组、线段树; |
| 60 | +* 数组单点修改,区间查询:**树状数组**、线段树; |
| 61 | +* 数组区间修改,单点查询:**差分**、线段树; |
| 62 | +* 数组区间修改,区间查询:**线段树**。 |
| 63 | + |
| 64 | +> 注意:上述总结是对于一般性而言的(能直接解决的),对标的是模板问题。 |
| 65 | +但存在经过一些经过"额外"操作,对问题进行转化,从而使用别的解决方案求解的情况。 |
| 66 | +例如某些问题,我们可以先对原数组进行差分,然后使用树状数组,也能解决区间修改问题。 |
| 67 | +或者使用多个树状数组来维护多个指标,从而实现类似线段树的持久化标记操作。 |
| 68 | +但这些不属于一般性,所以就不添加到题解了。 |
| 69 | + |
| 70 | +--- |
| 71 | + |
| 72 | +### 差分 |
| 73 | + |
| 74 | +本题只涉及「区间修改 + 单点查询」,因此是一道「差分」的模板题。 |
| 75 | + |
| 76 | +「差分」可以看做是求「前缀和」的逆向过程。 |
| 77 | + |
| 78 | +对于一个「将区间 $[l, r]$ 整体增加一个值 $v$」操作,我们可以对差分数组 $c$ 的影响看成两部分: |
| 79 | + |
| 80 | +* 对 $c[l] += v$:由于差分是前缀和的逆向过程,这个操作对于将来的查询而言,带来的影响是对于所有的下标大于等于 $l$ 的位置都增加了值 $v$; |
| 81 | +* 对 $c[r + 1] -= v$:由于我们期望只对 $[l, r]$ 产生影响,因此需要对下标大于 $r$ 的位置进行减值操作,从而抵消"影响"。 |
| 82 | + |
| 83 | +对于最后的构造答案,可看做是对每个下标做"单点查询"操作,只需要对差分数组求前缀和即可。 |
| 84 | + |
| 85 | +代码: |
| 86 | +```Java |
| 87 | +class Solution { |
| 88 | + public int[] corpFlightBookings(int[][] bs, int n) { |
| 89 | + int[] c = new int[n + 1]; |
| 90 | + for (int[] bo : bs) { |
| 91 | + int l = bo[0] - 1, r = bo[1] - 1, v = bo[2]; |
| 92 | + c[l] += v; |
| 93 | + c[r + 1] -= v; |
| 94 | + } |
| 95 | + int[] ans = new int[n]; |
| 96 | + ans[0] = c[0]; |
| 97 | + for (int i = 1; i < n; i++) { |
| 98 | + ans[i] = ans[i - 1] + c[i]; |
| 99 | + } |
| 100 | + return ans; |
| 101 | + } |
| 102 | +} |
| 103 | +``` |
| 104 | +* 时间复杂度:预处理差分数组的复杂度为 $O(n)$;构造答案复杂度为 $O(n)$。整体复杂度为 $O(n)$ |
| 105 | +* 空间复杂度:$O(n)$ |
| 106 | + |
| 107 | +--- |
| 108 | + |
| 109 | +### 线段树 |
| 110 | + |
| 111 | +在「基本分析」中,我们发现几乎所有的「区间求和」问题都可以使用线段树解决。 |
| 112 | + |
| 113 | +那么是否无脑写线段树呢?答案并不是,恰好相反。 |
| 114 | + |
| 115 | +线段树代码很长,且常数很大,实际表现不算很好。只有不得不写「线段树」的时候,我们才考虑线段树。 |
| 116 | + |
| 117 | +回到本题,由于涉及「区间修改」操作,因此我们需要对线段树进行持久化标记(懒标记),从而确保操作仍为 $\log$ 级别的复杂度。 |
| 118 | + |
| 119 | +代码: |
| 120 | +```Java |
| 121 | +class Solution { |
| 122 | + class Node { |
| 123 | + int l, r, v, add; |
| 124 | + Node(int _l, int _r) { |
| 125 | + l = _l; r = _r; |
| 126 | + } |
| 127 | + } |
| 128 | + int N = 20009; |
| 129 | + Node[] tr = new Node[N * 4]; |
| 130 | + void pushup(int u) { |
| 131 | + tr[u].v = tr[u << 1].v + tr[u << 1 | 1].v; |
| 132 | + } |
| 133 | + void pushdown(int u) { |
| 134 | + int add = tr[u].add; |
| 135 | + tr[u << 1].v += add; |
| 136 | + tr[u << 1].add += add; |
| 137 | + tr[u << 1 | 1].v += add; |
| 138 | + tr[u << 1 | 1].add += add; |
| 139 | + tr[u].add = 0; |
| 140 | + } |
| 141 | + void build(int u, int l, int r) { |
| 142 | + tr[u] = new Node(l, r); |
| 143 | + if (l != r) { |
| 144 | + int mid = l + r >> 1; |
| 145 | + build(u << 1, l, mid); |
| 146 | + build(u << 1 | 1, mid + 1, r); |
| 147 | + } |
| 148 | + } |
| 149 | + void update(int u, int l, int r, int v) { |
| 150 | + if (l <= tr[u].l && tr[u].r <= r) { |
| 151 | + tr[u].v += v; |
| 152 | + tr[u].add += v; |
| 153 | + } else { |
| 154 | + pushdown(u); |
| 155 | + int mid = tr[u].l + tr[u].r >> 1; |
| 156 | + if (l <= mid) update(u << 1, l, r, v); |
| 157 | + if (r > mid) update(u << 1 | 1, l, r, v); |
| 158 | + pushup(u); |
| 159 | + } |
| 160 | + } |
| 161 | + int query(int u, int l, int r) { |
| 162 | + if (l <= tr[u].l && tr[u].r <= r) { |
| 163 | + return tr[u].v; |
| 164 | + } else { |
| 165 | + pushdown(u); |
| 166 | + int mid = tr[u].l + tr[u].r >> 1; |
| 167 | + int ans = 0; |
| 168 | + if (l <= mid) ans += query(u << 1, l, r); |
| 169 | + if (r > mid) ans += query(u << 1 | 1, l, r); |
| 170 | + return ans; |
| 171 | + } |
| 172 | + } |
| 173 | + public int[] corpFlightBookings(int[][] bs, int n) { |
| 174 | + build(1, 1, n); |
| 175 | + for (int[] bo : bs) { |
| 176 | + update(1, bo[0], bo[1], bo[2]); |
| 177 | + } |
| 178 | + int[] ans = new int[n]; |
| 179 | + for (int i = 0; i < n; i++) { |
| 180 | + ans[i] = query(1, i + 1, i + 1); |
| 181 | + } |
| 182 | + return ans; |
| 183 | + } |
| 184 | +} |
| 185 | +``` |
| 186 | +* 时间复杂度:线段树建树复杂度为 $O(n),ドル其余操作复杂度为 $O(\log{n})$。对于本题,令 `bs` 长度为 $m,ドル整体复杂度为 $O(m\log{n} + n\log{n})$ |
| 187 | +* 空间复杂度:$O(n)$ |
| 188 | + |
| 189 | +--- |
| 190 | + |
| 191 | +### 最后 |
| 192 | + |
| 193 | +这是我们「刷穿 LeetCode」系列文章的第 `No.1109` 篇,系列开始于 2021年01月01日,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。 |
| 194 | + |
| 195 | +在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。 |
| 196 | + |
| 197 | +为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。 |
| 198 | + |
| 199 | +在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。 |
| 200 | + |
0 commit comments