3803. Count Residue Prefixes
Problem Description
You are given a string s consisting only of lowercase English letters.
A prefix of s is called a residue if the number of distinct characters in the prefix is equal to len(prefix) % 3.
Return the count of residue prefixes in s.
A prefix of a string is a non-empty substring that starts from the beginning of the string and extends to any point within it.
For each prefix, we look at two values: the number of distinct characters it contains, and the remainder when its length is divided by 3. If these two values match, the prefix qualifies as a residue prefix. The task is to count how many such prefixes exist.
For example, consider a prefix of length 3. Since 3 % 3 = 0, this prefix would only be a residue if it contained 0 distinct characters — which is impossible for a non-empty prefix, so it can never qualify. For a prefix of length 1, since 1 % 3 = 1, it is a residue when it has exactly 1 distinct character, which is always true. For a prefix of length 2, since 2 % 3 = 2, it is a residue only when both characters are different, giving 2 distinct characters.
How We Pick the Algorithm
Why Simulation / Basic DSA?
This problem maps to Simulation / Basic DSA through a short path in the full flowchart.
Counting prefixes where the number of distinct characters equals length % 3 is a simple incremental scan with a set to track distinct characters.
Open in FlowchartIntuition
The key observation is that prefixes have a natural nested structure: the prefix of length i is simply the prefix of length i - 1 with one more character appended. This means we don't need to recompute everything from scratch for each prefix — we can build up our information incrementally as we scan the string from left to right.
The only piece of information we need to track for each prefix is the number of distinct characters it contains. As we move from one character to the next, we just add the new character to a running collection of seen characters. A natural choice for this collection is a hash set, because it automatically ignores duplicates, so its size always tells us exactly how many distinct characters we have seen so far.
Once we have the distinct-character count for the current prefix, the rest is straightforward. The length of the current prefix is just its position index (counting from 1), so we compute len(prefix) % 3 and compare it with the size of the hash set. Whenever the two values are equal, the current prefix is a residue prefix, and we add 1 to our answer.
Because we process each character exactly once and only do constant-time work per character, this approach naturally arrives at an efficient single-pass solution.
Solution Approach
Solution 1: Hash Table
We use a hash table st to record the set of distinct characters that have appeared in the current prefix. We iterate through each character c in the string s, add it to the set st, and then check if the length of the current prefix modulo 3 equals the size of the set st. If they are equal, it means the current prefix is a residue prefix, and we increment the answer by 1.
Let's break down the implementation step by step:
-
Initialize an empty hash set
stto keep track of the distinct characters seen so far, and a counteransset to0to record the number of residue prefixes. -
Iterate over the string
susingenumerate(s, 1), so that the indexistarts from1. This indexidirectly represents the length of the current prefix. -
For each character
c, add it to the setst. After this, the size ofst(i.e.,len(st)) equals the number of distinct characters in the prefix of lengthi. -
Check whether
len(st) == i % 3. If this condition holds, the current prefix is a residue prefix, so we incrementansby1. -
After the iteration finishes, return
ansas the final answer.
The time complexity is O(n), where n is the length of the string s, since we process each character exactly once and each set operation takes constant time on average. The space complexity is O(|\Sigma|), where |\Sigma| is the size of the character set. Since s consists only of lowercase English letters, the set can hold at most 26 distinct characters, so the space used is constant.
Example Walkthrough
Let's walk through the solution using the small example s = "aba".
We maintain a hash set st to track distinct characters seen so far, and a counter ans initialized to 0. We iterate with index i starting from 1, where i represents the current prefix length.
Step 1: i = 1, c = 'a'
- Add
'a'tost, sost = {'a'}andlen(st) = 1. - Compute
i % 3 = 1 % 3 = 1. - Compare:
len(st) == i % 3→1 == 1✓ - The prefix
"a"is a residue prefix. Incrementansto1.
Step 2: i = 2, c = 'b'
- Add
'b'tost, sost = {'a', 'b'}andlen(st) = 2. - Compute
i % 3 = 2 % 3 = 2. - Compare:
len(st) == i % 3→2 == 2✓ - The prefix
"ab"is a residue prefix. Incrementansto2.
Step 3: i = 3, c = 'a'
- Add
'a'tost, but it already exists, sost = {'a', 'b'}andlen(st) = 2. - Compute
i % 3 = 3 % 3 = 0. - Compare:
len(st) == i % 3→2 == 0✗ - The prefix
"aba"is not a residue prefix.ansstays at2.
Result: After processing all characters, we return ans = 2.
This matches our intuition: the prefixes "a" (length 1, 1 distinct char) and "ab" (length 2, 2 distinct chars) qualify, while "aba" (length 3, requiring 0 distinct chars) cannot. Notice how the set was built incrementally — we never recomputed distinct counts from scratch, and the duplicate 'a' in Step 3 was automatically ignored by the set.
Solution Implementation
1class Solution:
2 def residuePrefixes(self, s: str) -> int:
3 # Set to track the distinct characters seen so far in the prefix
4 distinct_chars: set[str] = set()
5 # Counter for the number of qualifying prefixes
6 count = 0
7
8 # Iterate over the string; `length` is the 1-based length of the current prefix
9 for length, char in enumerate(s, start=1):
10 # Add the current character to the set of distinct characters
11 distinct_chars.add(char)
12
13 # Check if the number of distinct characters equals the prefix length modulo 3
14 if len(distinct_chars) == length % 3:
15 count += 1
16
17 return count
181class Solution {
2 public int residuePrefixes(String s) {
3 // Set to track distinct characters seen in the current prefix
4 Set<Character> distinctChars = new HashSet<>();
5
6 // Counter for prefixes that satisfy the condition
7 int count = 0;
8
9 // Iterate over each prefix of length i (1-based)
10 for (int i = 1; i <= s.length(); i++) {
11 // Current character at position (i - 1) in 0-based indexing
12 char currentChar = s.charAt(i - 1);
13
14 // Add the character to the set of distinct characters
15 distinctChars.add(currentChar);
16
17 // Check if the number of distinct characters equals (i mod 3)
18 if (distinctChars.size() == i % 3) {
19 count++;
20 }
21 }
22
23 return count;
24 }
25}
261class Solution {
2public:
3 int residuePrefixes(string s) {
4 // Tracks the set of distinct characters seen so far in the prefix.
5 unordered_set<char> distinctChars;
6
7 // Counts how many prefixes satisfy the condition.
8 int count = 0;
9
10 // Iterate over each prefix of length `length` (1-based).
11 for (int length = 1; length <= static_cast<int>(s.size()); ++length) {
12 // The character at the current end of the prefix (0-based index).
13 char currentChar = s[length - 1];
14
15 // Add it to the set of distinct characters.
16 distinctChars.insert(currentChar);
17
18 // Check whether the number of distinct characters equals
19 // the prefix length modulo 3.
20 if (static_cast<int>(distinctChars.size()) == length % 3) {
21 ++count;
22 }
23 }
24
25 return count;
26 }
27};
281/**
2 * Counts the number of prefixes whose count of distinct characters
3 * equals the prefix length taken modulo 3.
4 *
5 * For each prefix s[0..i], we track the set of distinct characters.
6 * The prefix length is (i + 1). If the number of distinct characters
7 * equals (i + 1) % 3, the prefix qualifies and is counted.
8 *
9 * @param s - The input string to scan.
10 * @returns The number of qualifying prefixes.
11 */
12function residuePrefixes(s: string): number {
13 // Holds the distinct characters encountered so far.
14 const distinctChars = new Set<string>();
15
16 // Accumulates the count of qualifying prefixes.
17 let count = 0;
18
19 for (let i = 0; i < s.length; i++) {
20 // Current character of the prefix ending at index i.
21 const currentChar = s[i];
22
23 // Add the character; duplicates are ignored by the Set.
24 distinctChars.add(currentChar);
25
26 // Current prefix length is (i + 1); compare its residue mod 3
27 // against the number of distinct characters in the prefix.
28 const prefixLengthResidue = (i + 1) % 3;
29
30 if (distinctChars.size === prefixLengthResidue) {
31 count++;
32 }
33 }
34
35 return count;
36}
37Time and Space Complexity
-
Time Complexity:
O(n), wherenis the length of the strings. The code iterates through the string with a single loop. For each character, the operations performed—adding the character to the set (st.add(c)), computing the set's length (len(st)), and the modulo comparison (i % 3)—all takeO(1)time on average. Therefore, the total time complexity isO(n). -
Space Complexity:
O(n), wherenis the length of the strings. The setstis used to store distinct characters encountered while traversing the string. In the worst case, when all characters insare unique, the set may hold up toncharacters, resulting inO(n)space usage.
Pattern Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Confusing "distinct characters in the prefix" with "total length of the prefix"
A very common mistake is to compare the prefix length itself against length % 3, instead of comparing the number of distinct characters against length % 3. These are two completely different quantities.
For example, for the prefix "aab" (length 3):
- The number of distinct characters is
2(aandb). - The prefix length is
3, and3 % 3 = 0.
The correct check compares distinct count (2) against length % 3 (0), which does not match. But if you mistakenly write if length == length % 3, you would get an entirely wrong comparison.
Incorrect:
for length, char in enumerate(s, start=1):
distinct_chars.add(char)
if length == length % 3: # WRONG: compares length with itself mod 3
count += 1
Correct:
for length, char in enumerate(s, start=1):
distinct_chars.add(char)
if len(distinct_chars) == length % 3: # compares distinct count vs length mod 3
count += 1
The key is to always use len(distinct_chars), not length, on the left side of the comparison.
Pitfall 2: Off-by-one error from 0-based indexing
The problem defines the prefix length as starting from 1 (a non-empty prefix of one character has length 1). If you iterate with the default enumerate(s) (which starts the index at 0), then your "length" value is actually index + 1, and forgetting this leads to an off-by-one error in the modulo calculation.
Incorrect:
for i, char in enumerate(s): # i starts at 0
distinct_chars.add(char)
if len(distinct_chars) == i % 3: # WRONG: i is index, not length
count += 1
Here, when processing the first character, i = 0, so i % 3 = 0, but len(distinct_chars) = 1. The condition fails even though a length-1 prefix should always qualify (1 % 3 = 1 and distinct count is 1).
Correct:
for i, char in enumerate(s):
distinct_chars.add(char)
length = i + 1 # convert to 1-based length
if len(distinct_chars) == length % 3:
count += 1
Or simply use enumerate(s, start=1) as in the original solution so that the loop variable directly represents the prefix length.
Pitfall 3: Resetting or misusing the distinct-character set
The set distinct_chars must accumulate characters across the entire iteration, because each prefix builds on the previous one. A mistake is to reset the set inside the loop or to recompute distinct characters from scratch for every prefix (which is correct but inefficient, turning an O(n) solution into O(n²)).
Incorrect (resets the set each iteration):
for length, char in enumerate(s, start=1):
distinct_chars = set() # WRONG: loses all previously seen characters
distinct_chars.add(char)
if len(distinct_chars) == length % 3:
count += 1
Inefficient (recomputes from scratch, O(n²)):
for length in range(1, len(s) + 1):
distinct_count = len(set(s[:length])) # rebuilds the set every time
if distinct_count == length % 3:
count += 1
Correct (incrementally grow the set): keep the set declared once before the loop and only add to it, so it always reflects the distinct characters of the current prefix in O(1) per step.
Ready to land your dream job?
Unlock your dream job with a 5-minute quiz for a personalized study roadmap!
Get My RoadmapA person thinks of a number between 1 and 1000. You may ask any number questions to them, provided that the question can be answered with either "yes" or "no".
What is the minimum number of questions you needed to ask so that you are guaranteed to know the number that the person is thinking?
Recommended Readings
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion If you prefer videos here's a video that explains recursion in a fun and easy way Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask him
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity describes how the time needed
Want a Structured Path to Master System Design Too? Don’t Miss This!