给定一个 下标从0开始 的字符串 s
,以及一个二维整数数组 queries
,其中 queries[i] = [li, ri]
表示 s
中从索引 li
开始到索引 ri
结束的子串(包括两端),即 s[li..ri]
。
返回一个数组 ans
,其中 ans[i]
是 queries[i]
的 同端 子串的数量。
如果一个 下标从0开始 且长度为 n
的字符串 t
两端的字符相同,即 t[0] == t[n - 1]
,则该字符串被称为 同端。
子串 是一个字符串中连续的非空字符序列。
示例 1:
输入:s = "abcaab", queries = [[0,0],[1,4],[2,5],[0,5]] 输出:[1,5,5,10] 解释:每个查询的同端子串如下: 第一个查询:s[0..0] 是 "a",有 1 个同端子串:"a"。 第二个查询:s[1..4] 是 "bcaa",有 5 个同端子串:"bcaa", "bcaa", "bcaa", "bcaa", "bcaa"。 第三个查询:s[2..5] 是 "caab",有 5 个同端子串:"caab", "caab", "caab", "caab", "caab"。 第四个查询:s[0..5] 是 "abcaab",有 10 个同端子串:"abcaab", "abcaab", "abcaab", "abcaab", "abcaab", "abcaab", "abcaab", "abcaab", "abcaab", "abcaab"。
示例 2:
输入:s = "abcd", queries = [[0,3]] 输出:[4] 解释:唯一的查询是 s[0..3],它有 4 个同端子串:"abcd", "abcd", "abcd", "abcd"。
提示:
2 <= s.length <= 3 * 10^4
s
仅包含小写英文字母。1 <= queries.length <= 3 * 10^4
queries[i] = [li, ri]
0 <= li <= ri < s.length
提示 1
If there are t
occurrences of a character in a substring, there exists t * (t - 1) / 2
Same-End substrings with that character.
提示 2
Try to calculate the number of occurrences of a character in a substring in O(1)
using partial sum.
解法:前缀和 + 哈希表
对于字符串 s 的下标范围是 [left,right] 的子串,如果该子串中的字符 c 出现次数是 count,则该子串中的以字符 c 为首尾字符的同端子串数量计算如下。
- 长度等于 1 的同端子串的数量是 count。
- 长度大于 1 的同端子串的数量是 count×(count−1) / 2 ,即从 count 个 c 中选择两个不同下标的字符 c 的方案数。
因此该子串中的以字符 c 为首尾字符的同端子串数量是 count+ count×(count−1) / 2 = count×(count+1) / 2 。
对于给定的子串下标范围 [left,right],只要得到该子串中的每个字符的出现次数,即可得到该子串中的同端子串的数量。
可以使用哈希表与前缀和的方式计算字符串 s 的每个前缀中的每个字符的出现次数,然后根据前缀和快速计算特定子串中的每个字符的出现次数。
用 n 表示字符串 s 的长度。对于 0≤i≤n 和 ‘a’≤c≤‘z’,使用 counts[i][c] 表示字符串 s 的长度为 i 的前缀中的字符 c 的出现次数。
- 当 i=0 时,前缀为空,因此对于任意字符 c 都有 counts[0][c]=0。
- 当 i>0 时,计算 counts[i][c] 的方法如下。
- 如果 s[i] = c,则 counts[i][c]=counts[i−1][c]+1。
- 如果 s[i] != c,则 counts[i][c]=counts[i−1][c]。
计算每个字符的出现次数的前缀和之后,即可根据前缀和计算特定子串中的每个字符的出现次数。
下标范围 [left,right] 的子串中的字符 c 的出现次数为 counts[right+1][c] − counts[left][c]。
遍历所有可能的字符 c 并计算该子串中的字符 c 的出现次数,即可得到该子串中的同端子串的数量。
Java版:
class Solution {
public int[] sameEndSubstringCount(String s, int[][] queries) {
int n = s.length();
int[][] counts = new int[n + 1][26];
for (int i = 0; i < n; i++) {
System.arraycopy(counts[i], 0, counts[i + 1], 0, 26);
counts[i + 1][s.charAt(i) - 'a']++;
}
int m = queries.length;
int[] ans = new int[m];
for (int i = 0; i < m; i++) {
int l = queries[i][0];
int r = queries[i][1];
int total = 0;
for (int j = 0; j < 26; j++) {
int count = counts[r + 1][j] - counts[l][j];
total += count * (count + 1) / 2;
}
ans[i] = total;
}
return ans;
}
}
Python3版:
class Solution:
def sameEndSubstringCount(self, s: str, queries: List[List[int]]) -> List[int]:
n = len(s)
counts = [[0] * 26 for _ in range(n + 1)]
for i, c in enumerate(s):
counts[i + 1] = counts[i].copy()
counts[i + 1][ord(c) - ord('a')] += 1
m = len(queries)
ans = [0] * m
for i, query in enumerate(queries):
total = 0
l = query[0]
r = query[1]
for j in range(26):
count = counts[r + 1][j] - counts[l][j]
total += count * (count + 1) // 2
ans[i] = total
return ans
复杂度分析
-
时间复杂度:O((n+q)×∣Σ∣),其中 n 是字符串 s 的长度,q 是数组 queries 的长度,Σ 是字符集,这道题中 Σ 是全部小写英语字母,∣Σ∣=26。计算每个字符的出现次数的前缀和的时间是 O(n×∣Σ∣),对于每个查询需要遍历所有可能的字符因此每个查询的计算时间都是 O(∣Σ∣),因此计算所有查询的时间是 O(q×∣Σ∣),时间复杂度是 O((n+q)×∣Σ∣)。
-
空间复杂度:O(n×∣Σ∣ + q),其中 n 是字符串 s 的长度,Σ 是字符集,这道题中 Σ 是全部小写英语字母,∣Σ∣=26。哈希表前缀和的空间是 O(n×∣Σ∣)。