Count Unique Characters of All Substrings of a Given String solution and explanation
9 more videos on this problem
Count Unique Characters of All Substrings of a Given String | Leetcode 828Techdose · 22:08 · 19K views
WALKTHROUGH - LeetCode 828. Count Unique Characters of All Substrings of a Given StringKacy Codes · 12:11 · 9K views
Leetcode 0828. Count Unique Characters of All Substrings of a Given StringFearless Learner · 8:29 · 2K views
【每日一题】LeetCode 828. Count Unique Characters of All Substrings of a Given StringHuifeng Guan · 16:26 · 1K views
COUNT UNIQUE CHARACTERS OF ALL SUBSTRINGS OF A GIVEN STRING | LEETCODE 828 | PYTHON SOLUTIONCracking FAANG · 29:13 · 1K views
828. Count Unique Characters of All Substrings of a Given String (Leetcode Hard)Programming Live with Larry · 27:08 · 933 views
Leetcode 828 Count Unique Characters of All Substrings of a Given StringRoss NA · 9:28 · 536 views
Heavenislost Leetcode #828 Count Unique Characters of All Substrings of a Given StringLei Feng · 13:11 · 347 views
leetcode 中文 | 828. Count Unique Characters of All Substrings of a Given String | Amazon高頻題 | Python今天比昨天厲害 · 23:01 · 299 views
Study Stream #1 - LeetCode 828 - Count Unique Characters Of All Substrings Of A Given StringAlpha-Code · 25:59 · 183 views
The problem
Let's define a function countUniqueChars(s) that returns the number of unique characters in s.
- For example, calling
countUniqueChars(s)ifs = "LEETCODE"then"L","T","C","O","D"are the unique characters since they appear only once ins, thereforecountUniqueChars(s) = 5.
Given a string s, return the sum of countUniqueChars(t) where t is a substring of s. The test cases are generated such that the answer fits in a 32-bit integer.
Notice that some substrings can be repeated so in this case you have to count the repeated ones too.
Example 1:
Input: s = "ABC" Output: 10 Explanation: All possible substrings are: "A","B","C","AB","BC" and "ABC". Every substring is composed with only unique letters. Sum of lengths of all substring is 1 + 1 + 1 + 2 + 2 + 3 = 10
Example 2:
Input: s = "ABA"
Output: 8
Explanation: The same as example 1, except countUniqueChars("ABA") = 1.
Example 3:
Input: s = "LEETCODE" Output: 92
Constraints:
1 <= s.length <= 105sconsists of uppercase English letters only.
Approach overview
Problem Overview: Given a string s, compute the sum of counts of unique characters for every possible substring. A character contributes to a substring only if it appears exactly once in that substring. The challenge is avoiding the naive enumeration of all substrings while correctly counting each character’s contribution.
Approach 1: Brute Force Enumeration (O(n^3) time, O(1) space)
Generate every substring using two nested loops and compute the number of unique characters in each substring. For each substring, iterate again to count frequencies and check which characters appear exactly once. This approach directly follows the problem definition but performs redundant work because many substrings share overlapping segments. It works for small inputs but quickly becomes impractical as n grows.
Approach 2: Optimized Contribution Technique (O(n) time, O(1) space)
Instead of evaluating substrings individually, calculate how many substrings each character contributes to as a unique character. Track the previous two occurrences of each character. If a character appears at index i, its contribution equals (i - prev) * (next - i), where prev and next represent the nearest identical characters on each side. This counts all substrings where the character is the only occurrence. Iterating through the string once accumulates the total contribution efficiently.
Approach 3: Hash Map for Fast Lookups (O(n) time, O(k) space)
Maintain a hash map that stores the last two positions for each character. While iterating through the string, update contributions based on these positions. Hash lookups make it easy to support larger alphabets beyond uppercase letters. This technique relies on the same contribution principle but uses a dynamic structure rather than fixed arrays. It combines ideas from hash tables and string processing.
Approach 4: Divide and Conquer via Binary Search (O(n log n) time, O(n) space)
Store all indices of each character in sorted lists. For every position, binary search the previous and next occurrences of that character to compute its contribution range. The substring contribution formula remains the same, but binary search replaces direct index tracking. This approach is useful when preprocessing character positions or when extending the logic to multiple queries on the same string.
Recommended for interviews: The contribution-based O(n) solution is the expected answer. It shows you understand how substring ranges work and how to convert a brute force counting problem into a per-character contribution problem. Explaining the brute force first demonstrates baseline reasoning, while the optimized approach highlights strong knowledge of dynamic programming-style state tracking and index arithmetic.
Approach 1: Brute Force Approach
This approach involves generating all possible substrings of the given string and calculating the number of unique characters for each substring. While this approach is simple, it is not efficient due to its O(n^3) complexity and is suitable only for small input sizes.
def countUniqueChars(s):
return len(set(s))
def sumOfUniqueCharsInAllSubstrings(s):
total_sum = 0
n = len(s)
for i in range(n):
for j in range(i+1, n+1):
total_sum += countUniqueChars(s[i:j])
return total_sum
# Example Usage
s = 'ABC'
print(sumOfUniqueCharsInAllSubstrings(s)) # Output: 10This Python function iterates over all possible substrings of the string s using two nested loops. For each substring, it calculates the number of unique characters using a set and accumulates the result.
The time complexity is O(n^3) because we explore all substrings (O(n^2)) and count unique characters (O(n)) for each. The space complexity is O(n) due to the set data structure.
Approach 2: Optimized Approach
This approach improves efficiency by keeping track of character positions as we iterate through the string, reducing the need to generate repetitive substrings. It utilizes two arrays to remember the last two positions of each character, to calculate their contribution effectively in linear time.
def uniqueLetterString(s):
index = {c: [-1, -1] for c in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'}
result = 0
for i, x in enumerate(s):
k, j = index[x]
result += (i - j) * (j - k)
index[x] = [j, i]
for c in index:
k, j = index[c]
result += (len(s) - j) * (j - k)
return result
# Example Usage
s = 'ABC'
print(uniqueLetterString(s)) # Output: 10This optimized Python function tracks the last two occurrences of each character in the string, updating a result variable in linear time. This avoids recomputing substrings and efficiently calculates the unique contribution of each character to all possible substrings.
The time complexity is O(n) since each character is processed a constant number of times. The space complexity is O(1) since we only store a fixed number of characters (26 for uppercase English letters).
Approach 3: Hash Map for Fast Lookups
This approach leverages a hash map to store the given data for constant-time average lookups. The idea is to map each unique element to its index and then use this map to quickly find relevant information when needed. This method allows us to efficiently locate elements and prevents duplicated work when searching for data. It's a commonly used technique when you need fast data retrieval.
def main():
hmap = {}
hmap[1] = 100
print("Value for key 1:", hmap[1]) # Outputs 100
main()import java.util.HashMap;
public class Main {
public static void main(String[] args) {
HashMap<Integer, Integer> map = new HashMap<>();
map.put(1, 100);
System.out.println("Value for key 1: " + map.get(1)); // Outputs 100
}
}#include <iostream>
#include <unordered_map>
int main() {
std::unordered_map<int, int> map;
map[1] = 100;
std::cout << "Value for key 1: " << map[1] << std::endl; // Outputs 100
return 0;
}let map = new Map();
map.set(1, 100);
console.log('Value for key 1:', map.get(1)); // Outputs 100#include <stdio.h>
#include <stdlib.h>
// Define a simple hash map node
typedef struct Node {
int key;
int value;
struct Node* next;
} Node;
#define MAP_SIZE 1000
// Hash function
int hash(int key) {
return key % MAP_SIZE;
}
// Insert into hash map
void insert(Node** map, int key, int value) {
int h = hash(key);
Node* newNode = malloc(sizeof(Node));
newNode->key = key;
newNode->value = value;
newNode->next = map[h];
map[h] = newNode;
}
// Search in hash map
int search(Node** map, int key) {
int h = hash(key);
Node* curr = map[h];
while (curr != NULL) {
if (curr->key == key) return curr->value;
curr = curr->next;
}
return -1; // Not found
}
int main() {
Node* map[MAP_SIZE] = { NULL };
insert(map, 1, 100);
printf("Value for key 1: %d\n", search(map, 1)); // Outputs 100
return 0;
}using System;
using System.Collections.Generic;
public class Program {
public static void Main(string[] args) {
Dictionary<int, int> map = new Dictionary<int, int>();
map[1] = 100;
Console.WriteLine("Value for key 1: " + map[1]); // Outputs 100
}
}Python's dictionary offers a map from keys to values with fast average-time access and update operations. This solution demonstrates storing and retrieving a value.
Time Complexity: O(1) on average per operation.
Space Complexity: O(n) for n items stored in the dictionary.
Approach 4: Divide and Conquer via Binary Search
This approach uses the strategy of divide-and-conquer through binary search to efficiently locate elements in a sorted structure. By splitting the search range in half with each step, the time complexity is significantly reduced compared to linear search. This technique is ideal for searching efficiently in data that can be sorted beforehand.
def binary_search(arr, target):
left, right = 0, len(arr) - 1
while left <= right:
mid = left + (right - left) // 2
if arr[mid] == target:
return mid
elif arr[mid] < target:
left = mid + 1
else:
right = mid - 1
return -1
arr = [1, 3, 5, 7, 9]
target = 5
print("Element found at index:", binary_search(arr, target)) # Outputs 2public class Main {
public static int binarySearch(int[] arr, int target) {
int left = 0, right = arr.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (arr[mid] == target)
return mid;
else if (arr[mid] < target)
left = mid + 1;
else
right = mid - 1;
}
return -1;
}
public static void main(String[] args) {
int[] arr = {1, 3, 5, 7, 9};
int target = 5;
System.out.println("Element found at index: " + binarySearch(arr, target)); // Outputs 2
}
}#include <iostream>
#include <vector>
#include <algorithm>
int binarySearch(const std::vector<int>& arr, int target) {
int left = 0, right = arr.size() - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (arr[mid] == target)
return mid;
else if (arr[mid] < target)
left = mid + 1;
else
right = mid - 1;
}
return -1;
}
int main() {
std::vector<int> arr = {1, 3, 5, 7, 9};
int target = 5;
int index = binarySearch(arr, target);
std::cout << "Element found at index: " << index << std::endl; // Outputs 2
return 0;
}function binarySearch(arr, target) {
let left = 0, right = arr.length - 1;
while (left <= right) {
let mid = Math.floor(left + (right - left) / 2);
if (arr[mid] === target) return mid;
else if (arr[mid] < target) left = mid + 1;
else right = mid - 1;
}
return -1;
}
let arr = [1, 3, 5, 7, 9];
let target = 5;
console.log('Element found at index:', binarySearch(arr, target)); // Outputs 2#include <stdio.h>
int binarySearch(int arr[], int size, int target) {
int left = 0, right = size - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (arr[mid] == target)
return mid;
else if (arr[mid] < target)
left = mid + 1;
else
right = mid - 1;
}
return -1;
}
int main() {
int arr[] = {1, 3, 5, 7, 9};
int target = 5;
int index = binarySearch(arr, 5, target);
printf("Element found at index: %d\n", index); // Outputs 2
return 0;
}using System;
class Program {
static int BinarySearch(int[] arr, int target) {
int left = 0, right = arr.Length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (arr[mid] == target)
return mid;
else if (arr[mid] < target)
left = mid + 1;
else
right = mid - 1;
}
return -1;
}
static void Main() {
int[] arr = {1, 3, 5, 7, 9};
int target = 5;
Console.WriteLine("Element found at index: " + BinarySearch(arr, target)); // Outputs 2
}
}In Python, binary search maintains a reduced search space until the sought-after element is located or the space is void, offering logarithmic efficiency.
Time Complexity: O(log n) per search attempt.
Space Complexity: O(1), as no extra space is required apart from constants.
Approach 5: Calculate the Contribution of Each Character
For each character c_i in the string s, when it appears only once in a substring, it contributes to the count of unique characters in that substring.
Therefore, we only need to calculate for each character c_i, how many substrings contain this character only once.
We use a hash table or an array d of length 26, to store the positions of each character in s in order of index.
For each character c_i, we iterate through each position p in d[c_i], find the adjacent positions l on the left and r on the right, then the number of substrings that meet the requirements by expanding from position p to both sides is (p - l) times (r - p). We perform this operation for each character, add up the contributions of all characters, and get the answer.
The time complexity is O(n), and the space complexity is O(n). Here, n is the length of the string s.
function uniqueLetterString(s: string): number {
const d: number[][] = Array.from({ length: 26 }, () => [-1]);
for (let i = 0; i < s.length; ++i) {
d[s.charCodeAt(i) - 'A'.charCodeAt(0)].push(i);
}
let ans = 0;
for (const v of d) {
v.push(s.length);
for (let i = 1; i < v.length - 1; ++i) {
ans += (v[i] - v[i - 1]) * (v[i + 1] - v[i]);
}
}
return ans;
}func uniqueLetterString(s string) (ans int) {
d := make([][]int, 26)
for i := range d {
d[i] = []int{-1}
}
for i, c := range s {
d[c-'A'] = append(d[c-'A'], i)
}
for _, v := range d {
v = append(v, len(s))
for i := 1; i < len(v)-1; i++ {
ans += (v[i] - v[i-1]) * (v[i+1] - v[i])
}
}
return
}impl Solution {
pub fn unique_letter_string(s: String) -> i32 {
let mut d: Vec<Vec<i32>> = vec![vec![-1; 1]; 26];
for (i, c) in s.chars().enumerate() {
d[(c as usize) - ('A' as usize)].push(i as i32);
}
let mut ans = 0;
for v in d.iter_mut() {
v.push(s.len() as i32);
for i in 1..v.len() - 1 {
ans += (v[i] - v[i - 1]) * (v[i + 1] - v[i]);
}
}
ans as i32
}
}Complexity comparison
| Approach | Complexity |
|---|---|
| Brute Force Approach | The time complexity is O(n^3) because we explore all substrings (O(n^2)) and count unique characters (O(n)) for each. The space complexity is O(n) due to the set data structure. |
| Optimized Approach | The time complexity is O(n) since each character is processed a constant number of times. The space complexity is O(1) since we only store a fixed number of characters (26 for uppercase English letters). |
| Hash Map for Fast Lookups | Time Complexity: O(1) on average per operation. |
| Divide and Conquer via Binary Search | Time Complexity: O(log n) per search attempt. |
| Calculate the Contribution of Each Character | – |
Complexity analysis
| Approach | Time | Space | When to Use |
|---|---|---|---|
| Brute Force Enumeration | O(n^3) | O(1) | Conceptual baseline or very small strings |
| Optimized Contribution Technique | O(n) | O(1) | Best general solution for interviews and large inputs |
| Hash Map for Fast Lookups | O(n) | O(k) | When alphabet size varies or characters extend beyond fixed arrays |
| Divide and Conquer with Binary Search | O(n log n) | O(n) | Useful when character positions are pre-indexed or reused across queries |
Frequently asked questions
Is Count Unique Characters of All Substrings of a Given String easy or hard?
LeetCode classifies this problem as Hard because the direct brute force idea is simple but inefficient. The challenge is discovering the contribution formula that converts substring enumeration into a linear scan using previous character positions.
Count Unique Characters of All Substrings of a Given String Python/Java solution
Python and Java implementations typically maintain arrays or hash maps for the last two occurrences of each character and update contribution values while iterating through the string. Both languages achieve the same O(n) time complexity with constant or small auxiliary space.
How to solve Count Unique Characters of All Substrings of a Given String in O(n)?
Track the previous and second‑previous occurrence of every character. When visiting index i, compute the number of substrings where the previous occurrence contributes as a unique character using (prev - prev2) * (i - prev). Accumulate these contributions and finalize remaining ones after the loop. This processes the string in a single pass.
What is the best approach for Count Unique Characters of All Substrings of a Given String?
The optimal solution uses the contribution technique. Instead of checking every substring, track the previous two occurrences of each character and compute how many substrings treat that character as unique. This allows computing each character’s contribution independently in O(n) time and O(1) space.
Is Count Unique Characters of All Substrings of a Given String asked at Google/Amazon/Meta?
Substring contribution and character counting problems frequently appear in interviews at companies like Google, Amazon, and Meta. Variants of this problem test understanding of string indexing, contribution counting, and optimization from brute force to linear time.
What data structure is used in Count Unique Characters of All Substrings of a Given String?
The main structures are arrays or hash maps that store the last two indices where each character appeared. These allow constant‑time updates while scanning the string. The approach combines hash table lookups with index arithmetic on strings.
What is the time complexity of Count Unique Characters of All Substrings of a Given String?
The optimal algorithm runs in O(n) time where n is the length of the string. Each character position is processed once while tracking its previous occurrences. Brute force solutions that generate all substrings can take O(n^3) time.
