这虽然是一个困难题,但实际上不难,不难的前提是理解并且掌握以下知识
828. 统计子串中的唯一字符
我们定义了一个函数 countUniqueChars(s)
来统计字符串 s
中的唯一字符,并返回唯一字符的个数。
例如:s = "LEETCODE"
,则其中 "L"
, "T"
,"C"
,"O"
,"D"
都是唯一字符,因为它们只出现一次,所以 countUniqueChars(s) = 5
。
本题将会给你一个字符串 s
,我们需要返回 countUniqueChars(t)
的总和,其中 t
是 s
的子字符串。输入用例保证返回值为 32 位整数。
注意,某些子字符串可能是重复的,但你统计时也必须算上这些重复的子字符串(也就是说,你必须统计 s
的所有子字符串中的唯一字符)。
示例 1:
输入: s = "ABC"
输出: 10
解释: 所有可能的子串为:"A","B","C","AB","BC" 和 "ABC"。
其中,每一个子串都由独特字符构成。
所以其长度总和为:1 + 1 + 1 + 2 + 2 + 3 = 10
示例 2:
输入: s = "ABA"
输出: 8
解释: 除了countUniqueChars
("ABA") = 1 之外,其余与示例 1 相同。
示例 3:
输入:s = "LEETCODE"
输出:92
首先需要了解的东西
1.会找一个字符串的所有子串
引用作者:capital-worker题解中的一张图
当前位置左边的数量乘上右边的数量就是当前位置能接触到的子数组的最大数量。
2.预处理数组
通过1我们知道要找与当前字符相同的左边位置和右边位置,最简单的方法就是预处理左右下标数组。用两个数组分别代表当前位置的左边第一次重复的位置和右边第一次重复的位置。
假定我们能预处理出两数组 l 和 r 分别代表 s[i]s[i] 作为子数组唯一字符时,其所能到达的最远两端:
- l[i] = a 代表下标 aa 为 s[i]s[i] 能够作为子数组唯一字符时的最远左边界,即为 s[i]s[i] 左边第一个与 s[i]s[i] 值相同的位置(若不存在,则为 a = -1a=−1)
- r[i] = b 代表跳表 bb 为 s[i]s[i] 能够作为子数组唯一字符时的最远右边界,即为 s[i]s[i] 右边第一个与 s[i]s[i] 值相同的位置(若不存在,则为 b = nb=n)n为字符串的长度
用for循环从左往右遍历,每个位置都将上一次出现当前字符的位置赋值给left数组(保存当前位置的左边第一次重复的位置),然后再将当前位置赋值到当前字符的数组位置。
同理从右往左遍历,就能找出当前位置的右边第一次重复的位置。
3.需要注意
子串只能是连续的,比如
ABCD中AB,BC,CD都是子串,AC之类不连续的就不是子串。
java代码实现
class Solution {
public int uniqueLetterString(String s) {
int n = s.length();
//1.建立两个辅助数组,用来保存当前位置左右两边的边界
int[] left = new int[n];
int[] right = new int[n];
int[] arr = new int[26];
//2.找出当前位置的左边的位置(左边没有和当前位置相同的就置为-1)
Arrays.fill(arr,-1); //将原始数组全部赋值为-1
//遍历字符串 ABA
for (int i = 0; i < n; i++) {
int index = s.charAt(i) - 'A';
left[i] = arr[index]; //将上一次出现当前字符的位置赋值给当前位置的左边
arr[index] = i; //更新当前字母出现的位置
}
//3.找出当前字符在右边第一次出现的位置
Arrays.fill(arr,n); //将原始数组全部赋值为n
for (int i = n -1 ; i >= 0 ; i--) {
int index = s.charAt(i) - 'A';
right[i] = arr[index]; //将上一次出现当前字符的位置赋值给当前位置的左边
arr[index] = i; //更新当前字母出现的位置
}
//4.重点:计算每个字符对出现次数的贡献
//通过分析得知:贡献为(a - l) * (r - a) a为当前字符的下标
int ans = 0;
for (int i = 0; i < n; i++) {
ans += (i - left[i] ) * (right[i] - i);
}
return ans;
}
}
参考:三叶大佬
网友评论