思路1(超时):
对于字符串s的子串s[i]...s[j]
,如何计算得到该子串的非重复字符数呢?这里考虑使用以下两个数组:
● lastRepeatIndex[]
:用于存储任一下标的字符其前一次出现的下标,初始化为-1
● nextRepeatIndex[]
:用于存储任一下标的字符其下一次出现的下标,初始化为s.length()
举一个简单的例子:
ABA
lastRepeatIndex = {-1,-1,0}
nextRepeatIndex = {2,3,3}
注意这两个数组一定是满足以下条件的,对于任意的下标i,有:
● i > lastRepeatIndex [i]
● i < nextRepeatIndex [i]
现在针对任意的子串s[i]...s[j]
,可以通过下面的方式计算不重复字符的数量:
对于i <= k <= j
,该子串中出现重复字符的条件为:lastRepeatIndex[k] >= i || nextRepeatIndex[k] <= j
;对于不满足该重复条件的字符,统计答案+1。
代码如下:
public class Solution {
public int uniqueLetterString(String s) {
Map<Character, Integer> repeatMap = new HashMap<>();
int[] lastRepeatIndex = new int[s.length()]; //前一个重复字符下标
int[] nextRepeatIndex = new int[s.length()]; //后一个重复字符下标
Arrays.fill(lastRepeatIndex, -1);
// Arrays.fill(nextRepeatIndex, -1);
Arrays.fill(nextRepeatIndex, s.length());
for (int i = 0; i < s.length(); i++) {
char key = s.charAt(i);
if (repeatMap.get(key) != null){
//已经出现过该字符
lastRepeatIndex[i] = repeatMap.get(key); //上一个重复的字符下标
}
repeatMap.put(key, i);
}
repeatMap.clear();
for (int i = s.length()-1; i >=0; i--) {
char key = s.charAt(i);
if (repeatMap.get(key) != null){
//已经出现过该字符
nextRepeatIndex[i] = repeatMap.get(key); //上一个重复的字符下标
}
repeatMap.put(key, i);
}
int ans = s.length(); //先把长度为1的计算加入
for (int i = 0; i < s.length(); i++) {
for (int j = i+1; j < s.length(); j++) {
//计算i-j之间的不重复字符长度
for (int k = i; k <= j; k++) {
//该子字符串中有重复字符的判断条件
if(!(lastRepeatIndex[k]>=i || nextRepeatIndex[k]<=j)){
ans++;
}
}
}
}
return ans;
}
}
时间复杂度为O( n 3 n^3 n3)
思路2
在思路1中,主要耗时的地方在于每次需要截取一个子串,并对改子串中每个不重复的字符进行统计并累加进ans中。
既然我们已经有了两个数组:lastRepeatIndex
和nextRepeatIndex
我们已经知道了对于任意一个字符是s[i]
,其前一次出现的下标j
以及下一次出现的下标k
前一次没有出现时,
lastRepeatIndex[i] = -1
下一次没有出现时,nextRepeatIndex[i] = s.length() = n
那么针对这个字符s[i]
,其对唯一字符的贡献度的计算思路如下:在s[j]...s[k]
(不包含边界字符)中包含s[i]
的子串的数量。计算方式如下:
● 子串起始位置个数:i-j
(包含s[i]
不包含s[j]
)
● 子串结束位置个数:k-i
(不包含s[k]
包含s[i]
)
因此其贡献度为(i-j)*(k-i)
因为只需要遍历一遍字符串,所以时间复杂度为O(n)
代码如下:
public class Solution {
public int uniqueLetterString(String s) {
Map<Character, Integer> repeatMap = new HashMap<>();
int[] lastRepeatIndex = new int[s.length()]; //前一个重复字符下标
int[] nextRepeatIndex = new int[s.length()]; //后一个重复字符下标
Arrays.fill(lastRepeatIndex, -1);
// Arrays.fill(nextRepeatIndex, -1);
Arrays.fill(nextRepeatIndex, s.length());
for (int i = 0; i < s.length(); i++) {
char key = s.charAt(i);
if (repeatMap.get(key) != null){
//已经出现过该字符
lastRepeatIndex[i] = repeatMap.get(key); //上一个重复的字符下标
}
repeatMap.put(key, i);
}
repeatMap.clear();
for (int i = s.length()-1; i >=0; i--) {
char key = s.charAt(i);
if (repeatMap.get(key) != null){
//已经出现过该字符
nextRepeatIndex[i] = repeatMap.get(key); //上一个重复的字符下标
}
repeatMap.put(key, i);
}
int ans = 0;
for (int i = 0; i < s.length(); i++) {
ans += (i-lastRepeatIndex[i])*(nextRepeatIndex[i]-i);
}
}
}