Given a string s
, you are to return the sum of counts of unique characters for all possible substrings of s
.
A character is unique in a substring if it appears exactly once in that substring.
For example, in the substring "ABC"
, all characters are unique, but in "ABA"
, only 'B'
is unique.
The task is: For every possible substring of s
, count the number of unique characters and sum these counts.
1 <= s.length <= 10^4
s
consists of uppercase English letters only.
The brute-force approach would be to generate all possible substrings of s
(which is O(N^2)
substrings for length N
), and for each substring, count the number of unique characters. However, this is far too slow for large N
because counting unique characters in each substring is also O(N)
, making it O(N^3)
overall.
To optimize, let's reverse the perspective: Instead of counting unique characters in every substring, consider each occurrence of a character and count in how many substrings it is unique. If we can compute this efficiently for each character, we can sum up these contributions for the answer.
The key insight is: For each occurrence of a character, we want to know in how many substrings it is the only occurrence of that character (i.e., it is unique in that substring).
We solve the problem by tracking, for each character in s
, the indices of its previous and next occurrence. This allows us to count, for each occurrence, how many substrings it is unique in.
s
.
i
of character c
:
prev
be the index of the previous occurrence of c
(-1
if none).next
be the index of the next occurrence of c
(len(s)
if none).s[i]
is unique is (i - prev) * (next - i)
.
prev
and end before next
, and must include i
.This approach is efficient because it processes each character occurrence in constant time, resulting in an overall linear time solution.
Let's use the example string "ABC"
:
Using the optimized approach:
Total = 3 + 4 + 3 = 10, which matches our earlier calculation.
O(N^3)
(O(N^2) substrings, O(N) per substring to count unique chars)O(1)
extra (not counting output or substrings themselves)O(N)
, because we only iterate through the string and each character's occurrences.O(N)
, for storing the occurrence indices of each character.This problem is efficiently solved by shifting perspective: Instead of evaluating every substring, we count each character's contribution as a unique character across all substrings where it is unique. By leveraging previous and next occurrence indices, we can compute each contribution in constant time, resulting in a linear time solution. The key insight is to count "from the character's point of view" rather than "from the substring's point of view."
class Solution:
def uniqueLetterString(self, s: str) -> int:
index = {}
for c in set(s):
index[c] = [-1]
for i, c in enumerate(s):
index[c].append(i)
for c in index:
index[c].append(len(s))
res = 0
for c in index:
arr = index[c]
for i in range(1, len(arr) - 1):
res += (arr[i] - arr[i-1]) * (arr[i+1] - arr[i])
return res
class Solution {
public:
int uniqueLetterString(string s) {
vector<int> index[26];
int n = s.size();
for (int i = 0; i < 26; ++i) index[i].push_back(-1);
for (int i = 0; i < n; ++i)
index[s[i] - 'A'].push_back(i);
for (int i = 0; i < 26; ++i) index[i].push_back(n);
int res = 0;
for (int c = 0; c < 26; ++c) {
for (int i = 1; i < index[c].size() - 1; ++i) {
res += (index[c][i] - index[c][i-1]) * (index[c][i+1] - index[c][i]);
}
}
return res;
}
};
class Solution {
public int uniqueLetterString(String s) {
List<Integer>[] index = new ArrayList[26];
int n = s.length();
for (int i = 0; i < 26; ++i) {
index[i] = new ArrayList<>();
index[i].add(-1);
}
for (int i = 0; i < n; ++i) {
index[s.charAt(i) - 'A'].add(i);
}
for (int i = 0; i < 26; ++i) {
index[i].add(n);
}
int res = 0;
for (int c = 0; c < 26; ++c) {
List<Integer> arr = index[c];
for (int i = 1; i < arr.size() - 1; ++i) {
res += (arr.get(i) - arr.get(i-1)) * (arr.get(i+1) - arr.get(i));
}
}
return res;
}
}
var uniqueLetterString = function(s) {
const index = {};
for (const c of new Set(s)) {
index[c] = [-1];
}
for (let i = 0; i < s.length; ++i) {
index[s[i]].push(i);
}
for (const c in index) {
index[c].push(s.length);
}
let res = 0;
for (const c in index) {
const arr = index[c];
for (let i = 1; i < arr.length - 1; ++i) {
res += (arr[i] - arr[i-1]) * (arr[i+1] - arr[i]);
}
}
return res;
};