Medium
题目描述
在一张长桌上,有一排盘子和蜡烛。给你一个下标从 0 开始的字符串 s,它只由字符 ‘’ 和 ‘|’ 组成,其中 ‘’ 表示一个盘子,’|’ 表示一个蜡烛。
同时给你一个下标从 0 开始的二维整数数组 queries,其中 queries[i] = [lefti, righti] 表示子字符串 s[lefti…righti](包含左右端点的字符)。对于每个查询,你需要找到子字符串中在蜡烛之间的盘子的数目。一个盘子被认为在蜡烛之间,当且仅当它左边和右边都至少有一个蜡烛。
例如,s = “|||||",查询 [3, 8] 表示的子字符串是 “||**|"。子字符串中在蜡烛之间的盘子数目为 2,因为每个盘子的左边和右边都在子字符串中至少有一个蜡烛。
返回一个整数数组 answer,其中 answer[i] 是第 i 个查询的答案。
示例 1:
输入:s = "**|**|***|", queries = [[2,5],[5,9]]
输出:[2,3]
解释:
- queries[0] 在蜡烛之间有两个盘子。
- queries[1] 在蜡烛之间有三个盘子。
示例 2:
输入:s = "***|**|*****|**||**|*", queries = [[1,17],[4,5],[14,17],[5,11],[15,16]]
输出:[9,0,0,0,0]
解释:
- queries[0] 在蜡烛之间有九个盘子。
- 其他查询在蜡烛之间都没有盘子。
提示:
- 3 <= s.length <= 10^5
- s 只包含字符 ‘*’ 和 ‘|’。
- 1 <= queries.length <= 10^5
- queries[i].length == 2
- 0 <= lefti <= righti < s.length
解题思路
解题思路分析:
这道题的核心是找到每个查询区间内最左边和最右边的蜡烛,然后计算它们之间的盘子数量。
主要思路:
预处理蜡烛位置:由于查询很多,我们需要快速找到区间内的第一个和最后一个蜡烛。可以预先记录所有蜡烛的位置,然后使用二分查找快速定位。
前缀和优化计算:为了快速计算两个蜡烛之间的盘子数量,我们可以使用前缀和数组。前缀和数组记录从开头到当前位置的盘子总数。
处理查询:
- 对于每个查询[left, right],使用二分查找找到区间内第一个蜡烛位置firstCandle和最后一个蜡烛位置lastCandle
- 如果找不到两个蜡烛或firstCandle >= lastCandle,则该区间内没有被蜡烛包围的盘子
- 否则,使用前缀和快速计算firstCandle和lastCandle之间的盘子数量
算法步骤:
- 遍历字符串s,记录所有蜡烛的位置到candlePos数组
- 构建前缀和数组prefixSum,prefixSum[i]表示s[0…i]中盘子的总数
- 对于每个查询,使用二分查找在candlePos中找到边界蜡烛
- 利用前缀和计算结果
这种方法的优势是预处理后每个查询都能在O(log n)时间内完成,整体效率很高。
代码实现
class Solution {
public:
vector<int> platesBetweenCandles(string s, vector<vector<int>>& queries) {
int n = s.length();
vector<int> candlePos;
vector<int> prefixSum(n + 1, 0);
// 记录蜡烛位置和构建前缀和
for (int i = 0; i < n; i++) {
if (s[i] == '|') {
candlePos.push_back(i);
}
prefixSum[i + 1] = prefixSum[i] + (s[i] == '*' ? 1 : 0);
}
vector<int> result;
for (auto& query : queries) {
int left = query[0], right = query[1];
// 找到区间内第一个蜡烛位置
auto it1 = lower_bound(candlePos.begin(), candlePos.end(), left);
// 找到区间内最后一个蜡烛位置
auto it2 = upper_bound(candlePos.begin(), candlePos.end(), right);
if (it1 == candlePos.end() || it2 == candlePos.begin() || it1 >= it2 - 1) {
result.push_back(0);
} else {
int firstCandle = *it1;
int lastCandle = *(it2 - 1);
result.push_back(prefixSum[lastCandle] - prefixSum[firstCandle + 1]);
}
}
return result;
}
};
class Solution:
def platesBetweenCandles(self, s: str, queries: List[List[int]]) -> List[int]:
import bisect
n = len(s)
candle_pos = []
prefix_sum = [0] * (n + 1)
# 记录蜡烛位置和构建前缀和
for i in range(n):
if s[i] == '|':
candle_pos.append(i)
prefix_sum[i + 1] = prefix_sum[i] + (1 if s[i] == '*' else 0)
result = []
for left, right in queries:
# 找到区间内第一个蜡烛位置
first_idx = bisect.bisect_left(candle_pos, left)
# 找到区间内最后一个蜡烛位置
last_idx = bisect.bisect_right(candle_pos, right) - 1
if first_idx >= len(candle_pos) or last_idx < 0 or first_idx >= last_idx:
result.append(0)
else:
first_candle = candle_pos[first_idx]
last_candle = candle_pos[last_idx]
result.append(prefix_sum[last_candle] - prefix_sum[first_candle + 1])
return result
public class Solution {
public int[] PlatesBetweenCandles(string s, int[][] queries) {
int n = s.Length;
List<int> candlePos = new List<int>();
int[] prefixSum = new int[n + 1];
// 记录蜡烛位置和构建前缀和
for (int i = 0; i < n; i++) {
if (s[i] == '|') {
candlePos.Add(i);
}
prefixSum[i + 1] = prefixSum[i] + (s[i] == '*' ? 1 : 0);
}
int[] result = new int[queries.Length];
for (int i = 0; i < queries.Length; i++) {
int left = queries[i][0], right = queries[i][1];
// 找到区间内第一个蜡烛位置
int firstIdx = BinarySearchLeft(candlePos, left);
// 找到区间内最后一个蜡烛位置
int lastIdx = BinarySearchRight(candlePos, right) - 1;
if (firstIdx >= candlePos.Count || lastIdx < 0 || firstIdx >= lastIdx) {
result[i] = 0;
} else {
int firstCandle = candlePos[firstIdx];
int lastCandle = candlePos[lastIdx];
result[i] = prefixSum[lastCandle] - prefixSum[firstCandle + 1];
}
}
return result;
}
private int BinarySearchLeft(List<int> arr, int target) {
int left = 0, right = arr.Count;
while (left < right) {
int mid = left + (right - left) / 2;
if (arr[mid] < target) {
left = mid + 1;
} else {
right = mid;
}
}
return left;
}
private int BinarySearchRight(List<int> arr, int target) {
int left = 0, right = arr.Count;
while (left < right) {
int mid = left + (right - left) / 2;
if (arr[mid] <= target) {
left = mid + 1;
} else {
right = mid;
}
}
return left;
}
}
/**
* @param {string} s
* @param {number[][]} queries
* @return {number[]}
*/
var platesBetweenCandles = function(s, queries) {
const n = s.length;
const candlePos = [];
const prefixSum = new Array(n + 1).fill(0);
// 记录蜡烛位置和构建前缀和
for (let i = 0; i < n; i++) {
if (s[i]
复杂度分析
| 复杂度类型 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | O(n + q log c) | n为字符串长度,q为查询数量,c为蜡烛数量。预处理O(n),每个查询二分查找O(log c) |
| 空间复杂度 | O(n + c) | 前缀和数组O(n),蜡烛位置数组O(c) |