前缀和
什么是前缀和?前缀和是一个数组的某项下标之前(包括此项元素)的所有数组元素的和。
题目
方法一:二分+前缀和
算法流程:
- 数据预处理:对字符串
s
从前往后扫描,将蜡烛下标记录到数组list
中(该数组严格递增-二分基础),并预处理出盘子的前缀和数组 - 遍历
queries
:- 对于任意一个查询
[
a
,
b
]
[a,b]
[a,b]而言,分别找出距离
a
最近的右蜡烛c
和距离b
最近的左蜡烛d
,此操作可通过对数组list
二分来实现,而查询区间 [ c , d ] [c, d] [c,d] 内的盘子数量」操作可直接查询「前缀和」数组
- 对于任意一个查询
[
a
,
b
]
[a,b]
[a,b]而言,分别找出距离
class Solution {
public int[] platesBetweenCandles(String s, int[][] queries) {
int n = s.length();
int m = queries.length;
int[] res = new int[m];
int[] preSum = new int[n];
List<Integer> list = new ArrayList<>();
// 数据预处理
for (int i = 0, sum = 0; i < n; i++) {
if (s.charAt(i) == '*') {
sum++;
}
// 记录 蜡烛 的下标
if (s.charAt(i) == '|') {
list.add(i);
}
// 前缀和
preSum[i] = sum;
}
if (list.size() == 0) {
// 没有蜡烛
return res;
}
// 遍历 queries
for (int i = 0; i < m; i++) {
int a = queries[i][0], b = queries[i][1];
int c = -1, d = -1;
// 找到 a 右边最近的蜡烛
int l = 0, r = list.size() - 1;
while (l < r) {
int mid = l + (r - l) / 2;
if (list.get(mid) < a) {
l = mid + 1;
} else {
r = mid;
}
}
if (list.get(r) >= a) {
c = list.get(r);
} else {
// 不存在
continue ;
}
// 找到 b 左边最近的蜡烛
l = 0;
r = list.size() - 1;
while (l < r) {
int mid = l + (r - l + 1) / 2;
if (list.get(mid) > b) {
r = mid - 1;
} else {
l = mid;
}
}
if (list.get(r) <= b) {
d = list.get(r);
} else {
// 不存在
continue ;
}
if (c <= d) {
res[i] = preSum[d] - preSum[c];
}
}
return res;
}
}
- 时间复杂度:令
s
的长度为 n n n,queries
长度为 m m m。统计所有的蜡烛并存入list
的复杂度为 O ( n ) O(n) O(n);预处理前缀和数组复杂度为 O ( n ) O(n) O(n);处理单个询问需要进行两次二分(蜡烛数量最多为 n n n),复杂度为 O ( log n ) O(\log{n}) O(logn),共有 m m m 个询问需要处理,复杂度为 O ( m ∗ log n ) O(m * \log{n}) O(m∗logn)。整体复杂度为 O ( n + m log n ) O(n + m\log{n}) O(n+mlogn) - 空间复杂度: O ( n ) O(n) O(n)
方法二:前缀和
在给定 s
的前提下,每个位置其左边和右边最近的蜡烛唯一确定,除了预处理前缀和,每个位置左右最近的蜡烛下标也可以预处理,从而省去每次二分。
算法流程:
-
数据预处理:
preSum
数组:前缀和数组,记录从区间 [ 0 , i ] [0,i] [0,i] 的*
的数量leftCandle
数组:记录位置i
左边最近的蜡烛rightCandle
数组:记录位置i
右边最近的蜡烛
-
遍历
queries
:- 对于任意一个查询
[
a
,
b
]
[a,b]
[a,b]而言,分别利用
rightCandle
和leftCandle
以 O ( 1 ) O(1) O(1)时间复杂度找到a
右边最近的蜡烛和b
左边最近的蜡烛,再利用preSum
前缀和数组求出盘子数量(通过预处理,将寻找蜡烛和计算盘子数量两个操作的时间复杂度都降至 O ( 1 ) O(1) O(1))
- 对于任意一个查询
[
a
,
b
]
[a,b]
[a,b]而言,分别利用
class Solution {
public int[] platesBetweenCandles(String s, int[][] queries) {
int n = s.length();
// preSum[i] 代表 [0,i] 的盘子数量
int[] preSum = new int[n];
for (int i = 0, sum = 0; i < n; i++) {
if (s.charAt(i) == '*') {
sum++;
}
preSum[i] = sum;
}
// leftCandle[i] 表示 i 左边最近的蜡烛位置
// 若 i 左边没有蜡烛,则 leftCandle[i] = -1;
int[] leftCandle = new int[n];
for (int i = 0, l = -1; i < n; i++) {
if (s.charAt(i) == '|') {
l = i;
}
leftCandle[i] = l;
}
// rightCandle[i] 表示 i 右边最近的蜡烛位置
// 若 i 右边没有蜡烛,则 rightCandle[i] = -1;
int[] rightCandle = new int[n];
for (int i = n - 1, r = -1; i >= 0; i--) {
if (s.charAt(i) == '|') {
r = i;
}
rightCandle[i] = r;
}
int[] res = new int[queries.length];
// 遍历 queries
for (int i = 0; i < queries.length; i++) {
int x = rightCandle[queries[i][0]];
int y = leftCandle[queries[i][1]];
res[i] = (x == -1 || y == -1 || x >= y) ? 0 : preSum[y] - preSum[x];
}
return res;
}
}
- 时间复杂度: O ( n + q ) O(n + q) O(n+q),其中 n n n 为数组长度, q q q 为询问数量
- 空间复杂度: O ( n ) O(n) O(n),其中 n n n 为数组长度。需要 O ( n ) O(n) O(n) 的空间保存预处理的结果