Hard
题目描述
存在一条无限长的数线,原点位于 0,向正 x 轴方向延伸。
给你一个二维数组 queries,包含两种类型的查询:
- 对于类型 1 的查询,
queries[i] = [1, x]。在距离原点 x 的位置建造一个障碍物。保证查询时在距离 x 处没有障碍物。 - 对于类型 2 的查询,
queries[i] = [2, x, sz]。检查是否可以在范围[0, x]内的任何位置放置一个大小为sz的区块,使得区块完全位于范围[0, x]内。区块不能与任何障碍物相交,但可以接触。注意,你实际上并不放置区块。查询是独立的。
返回一个布尔数组 results,其中 results[i] 为 true 表示可以放置第 i 个类型 2 查询中指定的区块,否则为 false。
示例 1:
输入:queries = [[1,2],[2,3,3],[2,3,1],[2,2,2]]
输出:[false,true,true]
解释:
查询 0:在 x = 2 处放置障碍物。最多可以在 x = 3 之前放置大小为 2 的区块。
示例 2:
输入:queries = [[1,7],[2,7,6],[1,2],[2,7,5],[2,7,6]]
输出:[true,true,false]
解释:
查询 0:在 x = 7 处放置障碍物。最多可以在 x = 7 之前放置大小为 7 的区块。
查询 2:在 x = 2 处放置障碍物。现在最多可以在 x = 7 之前放置大小为 5 的区块,在 x = 2 之前放置大小为 2 的区块。
约束:
1 <= queries.length <= 15 * 10^42 <= queries[i].length <= 31 <= queries[i][0] <= 21 <= x, sz <= min(5 * 10^4, 3 * queries.length)- 对于类型 1 的查询,保证查询时在距离 x 处不存在障碍物
- 保证至少有一个类型 2 的查询
解题思路
这道题的核心思路是维护每个位置到下一个障碍物的距离。
解题思路:
核心观察:要在区间
[0, x]中放置大小为sz的区块,我们需要找到一个连续的空间,其长度至少为sz。如果我们知道每个位置到下一个障碍物的距离,那么问题就转化为:在区间[0, x-sz]中是否存在某个位置i,使得从位置i到下一个障碍物的距离大于等于sz。数据结构选择:使用线段树来维护区间最大值。对于每个位置
i,我们维护d[i]表示从位置i到下一个障碍物的距离。算法流程:
- 初始化时,假设在很远的地方(如
50001)有一个虚拟障碍物 - 对于类型 1 查询(添加障碍物):在位置
x添加障碍物,更新所有受影响位置的d值 - 对于类型 2 查询(检查是否可放置区块):查询区间
[0, x-sz]的最大d值是否大于等于sz
- 初始化时,假设在很远的地方(如
优化细节:
- 使用坐标压缩,只处理查询中出现的坐标
- 线段树支持单点更新和区间查询最大值
- 添加障碍物时,需要更新该障碍物前面所有位置到下一个障碍物的距离
代码实现
class Solution {
public:
vector<bool> getResults(vector<vector<int>>& queries) {
set<int> obstacles;
obstacles.insert(50001); // 虚拟障碍物
vector<bool> results;
for (auto& query : queries) {
if (query[0] == 1) {
// 添加障碍物
obstacles.insert(query[1]);
} else {
// 检查是否可以放置区块
int x = query[1], sz = query[2];
if (sz > x) {
results.push_back(false);
continue;
}
bool canPlace = false;
// 检查每个可能的起始位置
for (int start = 0; start <= x - sz; start++) {
// 找到start位置之后的第一个障碍物
auto it = obstacles.upper_bound(start);
int nextObstacle = *it;
if (start + sz <= min(nextObstacle, x)) {
canPlace = true;
break;
}
}
results.push_back(canPlace);
}
}
return results;
}
};
class Solution:
def getResults(self, queries: List[List[int]]) -> List[bool]:
import bisect
obstacles = [50001] # 虚拟障碍物
results = []
for query in queries:
if query[0] == 1:
# 添加障碍物
bisect.insort(obstacles, query[1])
else:
# 检查是否可以放置区块
x, sz = query[1], query[2]
if sz > x:
results.append(False)
continue
can_place = False
# 检查每个可能的起始位置
for start in range(x - sz + 1):
# 找到start位置之后的第一个障碍物
idx = bisect.bisect_right(obstacles, start)
next_obstacle = obstacles[idx]
if start + sz <= min(next_obstacle, x):
can_place = True
break
results.append(can_place)
return results
public class Solution {
public IList<bool> GetResults(int[][] queries) {
var obstacles = new SortedSet<int>();
obstacles.Add(50001); // 虚拟障碍物
var results = new List<bool>();
foreach (var query in queries) {
if (query[0] == 1) {
// 添加障碍物
obstacles.Add(query[1]);
} else {
// 检查是否可以放置区块
int x = query[1], sz = query[2];
if (sz > x) {
results.Add(false);
continue;
}
bool canPlace = false;
// 检查每个可能的起始位置
for (int start = 0; start <= x - sz; start++) {
// 找到start位置之后的第一个障碍物
int nextObstacle = obstacles.GetViewBetween(start + 1, int.MaxValue).Min;
if (start + sz <= Math.Min(nextObstacle, x)) {
canPlace = true;
break;
}
}
results.Add(canPlace);
}
}
return results;
}
}
var getResults = function(queries) {
const obstacles = new Set([50001]); // 虚拟障碍物
const results = [];
for (const query of queries) {
if (query[0]
复杂度分析
| 复杂度类型 | 分析 |
|---|---|
| 时间复杂度 | O(n × m),其中 n 是查询数量,m 是坐标范围。对于每个类型2查询,最坏情况下需要检查 O(m) 个起始位置 |
| 空间复杂度 | O(k),其中 k 是障碍物的数量,用于存储障碍物位置 |
相关题目
. Building Boxes (Hard)
. Fruits Into Baskets III (Medium)