Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情。
给你一个长桌子,桌子上盘子和蜡烛排成一列。给你一个下标从 0 开始的字符串 s ,它只包含字符 '' 和 '|' ,其中 '' 表示一个 盘子 ,'|' 表示一支 蜡烛 。同时给你一个下标从 0 开始的二维整数数组 queries ,其中 queries[i] = [lefti, righti] 表示 子字符串 s[lefti...righti] (包含左右端点的字符)。对于每个查询,你需要找到 子字符串中 在 两支蜡烛之间 的盘子的 数目 。如果一个盘子在 子字符串中 左边和右边 都 至少有一支蜡烛,那么这个盘子满足在 两支蜡烛之间 。比方说,s = "|||||" ,查询 [3, 8] ,表示的是子字符串 "||**|" 。子字符串中在两支蜡烛之间的盘子数目为 2 ,子字符串中右边两个盘子在它们左边和右边 都 至少有一支蜡烛。 请你返回一个整数数组 answer ,其中 answer[i] 是第 i 个查询的答案。
此题亦可以用暴力穷举的方式求解,只需遍历到指定区域的两根蜡烛,得到其中的盘子个数,最后相加得到总和,不过暴力穷举的时间复杂度为O(n^3),所以不考虑。
此题可以用前缀和的方式求解,在这之前,我们先来分析一下这道题,以题目中的示例2为例:
若是想求得范围[1,17]中有多少个盘子在蜡烛之间,则需要求解的范围如下:
不难发现,下标[1]、[2]、[17]位置的元素我们压根不用管,因为它们不在两根蜡烛之间,那么问题可以转换为以下思路:
- 首先求得左边界距离最近的蜡烛,记为c1
- 其次求得右边界最近的蜡烛,记为c2
- 最后求解c1与c2之间的盘子数即可
分别来实现这三个过程,首先求解左边界距离最近的蜡烛,我们只需从左往右进行遍历,若是找到蜡烛则记录下标,如:
对于前3个元素,因为都是盘子,所以全部标记为-1,直至遇到第4个元素为蜡烛,标记为下标3:
后面的盘子也都标记为3,直至遇到新的蜡烛,就更新标记,以此类推,即可得到任意位置左边界距离最近的蜡烛。
代码如下:
private static int[] getPreCandleIndex(String s) {
int[] ans = new int[s.length()];
int temp = -1;
for (int i = 0; i < s.length(); ++i) {
// 遇到蜡烛则更新下标
if (s.charAt(i) == '|') {
temp = i;
}
ans[i] = temp;
}
return ans;
}
求解右边界距离最近的蜡烛也是如此,只不过是从右往左遍历,代码如下:
private static int[] getNextCandleIndex(String s) {
int[] ans = new int[s.length()];
int temp = -1;
for (int i = s.length() - 1; i >= 0; --i) {
if (s.charAt(i) == '|') {
temp = i;
}
ans[i] = temp;
}
return ans;
}
最后是第三步,求解c1到c2区间内的盘子总数,这里我们可以使用前缀和,先将每个位置前面有多少个盘子求出来:
private static int[] getPrePlatesSum(String s) {
int[] ans = new int[s.length()];
for (int i = 0; i < s.length(); ++i) {
// 第一个位置前缀和为0
if (i == 0) {
ans[i] = 0;
} else {
// 首先得到前一个位置的前缀和
ans[i] = ans[i] + ans[i - 1];
}
// 若当前位置为盘子,和加1
if (s.charAt(i) == '*') {
++ans[i];
}
}
return ans;
}
这样就得到了任意位置前有多少个盘子了,现在若是想求某个区间的盘子数,只需让右侧区间位置的前缀和减去左侧区间位置的前缀和即可。
还需要注意一些特殊情况,如:
对于范围[0,2],它找不到左侧距离最近的蜡烛,又如:
对于范围[20],它找不到右侧距离最近的蜡烛,还有:
对于范围[4,5],它左侧距离最近的蜡烛下标为6,右侧距离最近的蜡烛下标为3,此时左侧蜡烛下标大于右侧蜡烛下标。
以上三种特殊情况,其盘子总数均为0。
综上所述,代码如下:
public static int[] platesBetweenCandles(String s, int[][] queries) {
int[] ans = new int[queries.length];
// 计算左侧距离最近的蜡烛下标
int[] preCandleIndex = getPreCandleIndex(s);
// 计算右侧距离最近的蜡烛下标
int[] nextCandleIndex = getNextCandleIndex(s);
// 计算前缀和
int[] prePlatesSum = getPrePlatesSum(s);
for (int i = 0; i < queries.length; ++i) {
// 得到左侧最近的蜡烛下标
int left = nextCandleIndex[queries[i][0]];
// 得到右侧最近的蜡烛下标
int right = preCandleIndex[queries[i][1]];
// 处理特殊情况
if (left == -1 || right == -1 || left > right) {
ans[i] = 0;
} else {
// 前缀和相减
ans[i] = prePlatesSum[right] - prePlatesSum[left];
}
}
return ans;
}
private static int[] getPrePlatesSum(String s) {
int[] ans = new int[s.length()];
for (int i = 0; i < s.length(); ++i) {
// 第一个位置前缀和为0
if (i == 0) {
ans[i] = 0;
} else {
// 首先得到前一个位置的前缀和
ans[i] = ans[i] + ans[i - 1];
}
// 若当前位置为盘子,和加1
if (s.charAt(i) == '*') {
++ans[i];
}
}
return ans;
}
private static int[] getNextCandleIndex(String s) {
int[] ans = new int[s.length()];
int temp = -1;
for (int i = s.length() - 1; i >= 0; --i) {
if (s.charAt(i) == '|') {
temp = i;
}
ans[i] = temp;
}
return ans;
}
private static int[] getPreCandleIndex(String s) {
int[] ans = new int[s.length()];
int temp = -1;
for (int i = 0; i < s.length(); ++i) {
// 遇到蜡烛则更新下标
if (s.charAt(i) == '|') {
temp = i;
}
ans[i] = temp;
}
return ans;
}
此题得解。