1933. Check if String Is Decomposable Into Value-Equal Substrings 🔒
Problem Description
You are given a digit string s
consisting of characters. The goal is to check if you can split this string into consecutive substrings that meet specific conditions.
A value-equal string is defined as a string where all characters are identical. For example:
"1111"
is value-equal (all 1's)"33"
is value-equal (all 3's)"123"
is NOT value-equal (different characters)
The task is to decompose the input string s
into consecutive value-equal substrings with these requirements:
- Exactly one substring must have a length of
2
- All other substrings must have a length of
3
- The substrings must be consecutive (no gaps or overlaps)
- Each substring must be value-equal
Return true
if such a decomposition is possible, otherwise return false
.
For example:
- If
s = "000111000"
, this can be decomposed as"000"
+"111"
+"000"
(all length 3, but missing the required length-2 substring), so it would returnfalse
- If
s = "00011111222"
, this could be decomposed as"000"
+"11"
+"111"
+"222"
, which has exactly one length-2 substring and the rest are length-3, so it would returntrue
The key challenge is ensuring that when you group consecutive identical characters, the groups can be arranged to have exactly one group of size 2 and all others of size 3.
Intuition
When we look at this problem, we need to group consecutive identical characters and then check if these groups can be split into valid lengths (one group of length 2, rest of length 3).
The key insight is that when we have a group of consecutive identical characters, we can split them in different ways. For example, if we have "11111"
(5 ones), we can split it as "11" + "111"
(length 2 + length 3) or keep it as one group of length 5.
Let's think about what happens with different group lengths:
- Length 1: Cannot form either a 2 or 3, so it's impossible
- Length 2: Perfect for our required single length-2 substring
- Length 3: Perfect for a length-3 substring
- Length 4: Can be split as 2+2, but we need exactly one length-2, so this is problematic
- Length 5: Can be split as 2+3, giving us one length-2 and one length-3
- Length 6: Can be split as 3+3, giving us two length-3 substrings
- Length 7: Can be split as 2+2+3, but that gives us two length-2 substrings (not allowed)
- Length 8: Can be split as 2+3+3, giving us one length-2 and two length-3
We can see a pattern emerging. For any group of length n
:
- If
n % 3 == 0
: We can split it into groups of 3 - If
n % 3 == 1
: We have a remainder of 1, which cannot form a valid group (neither 2 nor 3) - If
n % 3 == 2
: We can use one length-2 substring and the rest as length-3
This leads to the solution strategy: traverse the string, identify each group of consecutive identical characters, and check if its length modulo 3 equals 1 (impossible case). Count how many groups have length modulo 3 equal to 2 (these contribute a length-2 substring). We need exactly one such group for the string to be decomposable according to the rules.
Solution Approach
The solution uses a two-pointer approach to identify and process groups of consecutive identical characters.
We initialize two variables:
i
: Starting position of the current groupcnt2
: Counter to track how many groups have contributed a length-2 substring
The algorithm works as follows:
-
Traverse the string using pointer
i
that marks the start of each group of identical characters. -
Find the end of current group: For each position
i
, use pointerj
to find where the current group ends by advancingj
whiles[j] == s[i]
. This gives us a group of length(j - i)
. -
Check the group length modulo 3:
-
If
(j - i) % 3 == 1
: This group has a remainder of 1 when divided by 3, which cannot be decomposed into valid substrings (neither 2 nor 3). Returnfalse
immediately. -
If
(j - i) % 3 == 2
: This group can contribute one length-2 substring. Incrementcnt2
to track this. Ifcnt2 > 1
, we have more than one length-2 substring, which violates the requirement, so returnfalse
. -
If
(j - i) % 3 == 0
: This group can be perfectly divided into length-3 substrings, so we continue without any special action.
-
-
Move to the next group: Set
i = j
to start processing the next group. -
Final validation: After processing all groups, check if
cnt2 == 1
. We need exactly one length-2 substring for a valid decomposition.
The time complexity is O(n)
where n
is the length of the string, as we traverse the string once. The space complexity is O(1)
as we only use a constant amount of extra space for variables.
This approach efficiently checks whether the string can be decomposed according to the rules without actually creating the substrings, making it both time and space efficient.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with the example s = "00011111222"
.
Step 1: Initialize variables
i = 0
(starting position)cnt2 = 0
(count of groups contributing a length-2 substring)
Step 2: Process first group (000)
- Start at
i = 0
, character is '0' - Find end:
j
advances whiles[j] == '0'
, stops atj = 3
- Group length:
3 - 0 = 3
- Check:
3 % 3 = 0
(perfectly divisible by 3, no action needed) - Move:
i = 3
Step 3: Process second group (11111)
- Start at
i = 3
, character is '1' - Find end:
j
advances whiles[j] == '1'
, stops atj = 8
- Group length:
8 - 3 = 5
- Check:
5 % 3 = 2
(remainder 2, can contribute one length-2) - Action: Increment
cnt2 = 1
(first length-2 substring found) - Note: This group of 5 can be split as "11" + "111"
- Move:
i = 8
Step 4: Process third group (222)
- Start at
i = 8
, character is '2' - Find end:
j
advances whiles[j] == '2'
, stops atj = 11
(end of string) - Group length:
11 - 8 = 3
- Check:
3 % 3 = 0
(perfectly divisible by 3, no action needed) - Move:
i = 11
(end of string)
Step 5: Final validation
- Check:
cnt2 == 1
? Yes! We have exactly one length-2 substring - Return:
true
The decomposition would be: "000" + "11" + "111" + "222" (one length-2 and three length-3 substrings).
Counter-example: If we had s = "0001111"
(3 zeros, 4 ones):
- First group: length 3,
3 % 3 = 0
✓ - Second group: length 4,
4 % 3 = 1
✗ - Return
false
immediately (length 4 cannot be split into valid combinations)
Solution Implementation
1class Solution:
2 def isDecomposable(self, s: str) -> bool:
3 """
4 Check if string s can be decomposed into value-equal triplets
5 with exactly one value-equal pair.
6
7 A string is decomposable if it consists of groups where:
8 - Exactly one group has length 2 (a pair)
9 - All other groups have length 3 (triplets)
10 - Each group contains only identical characters
11
12 Args:
13 s: Input string to check
14
15 Returns:
16 True if string is decomposable, False otherwise
17 """
18 i = 0
19 n = len(s)
20 pair_count = 0 # Count of groups with length 2
21
22 # Process the string in groups of identical consecutive characters
23 while i < n:
24 j = i
25
26 # Find the end of current group of identical characters
27 while j < n and s[j] == s[i]:
28 j += 1
29
30 group_length = j - i
31
32 # If group length leaves remainder 1 when divided by 3,
33 # it cannot form valid triplets or a pair
34 if group_length % 3 == 1:
35 return False
36
37 # If group length leaves remainder 2 when divided by 3,
38 # it forms triplets and one pair
39 if group_length % 3 == 2:
40 pair_count += 1
41
42 # More than one pair makes decomposition impossible
43 if pair_count > 1:
44 return False
45
46 # Move to the next group
47 i = j
48
49 # Valid decomposition requires exactly one pair
50 return pair_count == 1
51
1class Solution {
2 public boolean isDecomposable(String s) {
3 int currentIndex = 0;
4 int stringLength = s.length();
5 int countOfLengthTwo = 0; // Track number of segments with length divisible by 3 with remainder 2
6
7 // Process the string by grouping consecutive identical characters
8 while (currentIndex < stringLength) {
9 int segmentStart = currentIndex;
10
11 // Find the end of current segment of identical characters
12 while (currentIndex < stringLength &&
13 s.charAt(currentIndex) == s.charAt(segmentStart)) {
14 currentIndex++;
15 }
16
17 int segmentLength = currentIndex - segmentStart;
18
19 // Check if segment length modulo 3 equals 1 (cannot be decomposed)
20 if (segmentLength % 3 == 1) {
21 return false; // Cannot form valid groups of 2 or 3
22 }
23
24 // Check if segment length modulo 3 equals 2 (needs one group of 2)
25 if (segmentLength % 3 == 2) {
26 countOfLengthTwo++;
27 // More than one segment requiring a group of 2 is invalid
28 if (countOfLengthTwo > 1) {
29 return false;
30 }
31 }
32
33 // Move to the next segment (already updated in the inner while loop)
34 }
35
36 // Valid decomposition requires exactly one segment with remainder 2
37 return countOfLengthTwo == 1;
38 }
39}
40
1class Solution {
2public:
3 bool isDecomposable(string s) {
4 int countGroupsOfTwo = 0; // Track the number of groups with length % 3 == 2
5
6 // Iterate through the string using two pointers
7 for (int startIdx = 0, strLength = s.size(); startIdx < strLength;) {
8 int endIdx = startIdx;
9
10 // Find the end of the current group of identical characters
11 while (endIdx < strLength && s[endIdx] == s[startIdx]) {
12 ++endIdx;
13 }
14
15 int groupLength = endIdx - startIdx;
16
17 // If group length % 3 == 1, it cannot be decomposed into groups of 2 or 3
18 if (groupLength % 3 == 1) {
19 return false;
20 }
21
22 // If group length % 3 == 2, we need exactly one such group
23 // (it will form one group of 2, and the rest will be groups of 3)
24 if (groupLength % 3 == 2) {
25 countGroupsOfTwo++;
26 }
27
28 // We can have at most one group with length % 3 == 2
29 if (countGroupsOfTwo > 1) {
30 return false;
31 }
32
33 // Move to the next group of characters
34 startIdx = endIdx;
35 }
36
37 // The string is decomposable if we have exactly one group with length % 3 == 2
38 return countGroupsOfTwo == 1;
39 }
40};
41
1/**
2 * Checks if a string can be decomposed into groups where each character appears
3 * in multiples of 3, except for exactly one character which appears in a group
4 * with length that leaves remainder 2 when divided by 3.
5 *
6 * @param s - The input string to check for decomposability
7 * @returns true if the string is decomposable according to the rules, false otherwise
8 */
9function isDecomposable(s: string): boolean {
10 const stringLength: number = s.length;
11 let groupsWithRemainder2Count: number = 0;
12
13 // Iterate through the string by groups of consecutive identical characters
14 for (let currentIndex: number = 0; currentIndex < stringLength; ) {
15 let nextDifferentCharIndex: number = currentIndex;
16
17 // Find the end of the current group of identical characters
18 while (nextDifferentCharIndex < stringLength &&
19 s[nextDifferentCharIndex] === s[currentIndex]) {
20 nextDifferentCharIndex++;
21 }
22
23 const currentGroupLength: number = nextDifferentCharIndex - currentIndex;
24
25 // If group length leaves remainder 1 when divided by 3, it's invalid
26 if (currentGroupLength % 3 === 1) {
27 return false;
28 }
29
30 // If group length leaves remainder 2 when divided by 3
31 // We can only have exactly one such group
32 if (currentGroupLength % 3 === 2) {
33 groupsWithRemainder2Count++;
34 if (groupsWithRemainder2Count > 1) {
35 return false;
36 }
37 }
38
39 // Move to the next group of characters
40 currentIndex = nextDifferentCharIndex;
41 }
42
43 // The string is decomposable only if there's exactly one group with remainder 2
44 return groupsWithRemainder2Count === 1;
45}
46
Time and Space Complexity
Time Complexity: O(n)
, where n
is the length of the string s
.
The algorithm uses a single pass through the string with two pointers i
and j
. The outer while loop iterates through the string, and for each position i
, the inner while loop moves j
forward to find consecutive identical characters. Although there are nested loops, each character in the string is visited exactly once by the pointer j
. The pointer i
jumps to the position of j
after each group of identical characters is processed, ensuring no character is revisited. Therefore, the total number of operations is proportional to n
.
Space Complexity: O(1)
.
The algorithm only uses a constant amount of extra space regardless of the input size. The variables used are:
i
andj
: two integer pointersn
: storing the length of the stringcnt2
: a counter for groups with remainder 2 when divided by 3
All these variables occupy constant space that doesn't scale with the input size n
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Misunderstanding Group Length Requirements
The Mistake: A common misconception is thinking that each group of consecutive identical characters must be either length 2 OR length 3. This leads to incorrect logic like:
# INCORRECT approach while i < n: j = i while j < n and s[j] == s[i]: j += 1 group_length = j - i # Wrong: assuming each group must be exactly 2 or 3 if group_length == 2: pair_count += 1 elif group_length != 3: return False # This is wrong!
Why It's Wrong: Groups can be longer than 3! For example, a group of 5 identical characters ("11111") can be split into one length-3 substring ("111") and one length-2 substring ("11"). The key insight is that groups need to be divisible into combinations of 2s and 3s.
The Correct Understanding:
- Length % 3 == 0: Can be perfectly divided into 3s (e.g., 6 → 3+3)
- Length % 3 == 1: Cannot be divided into 2s and 3s (e.g., 4 → impossible)
- Length % 3 == 2: Can be divided into 3s with one 2 left over (e.g., 5 → 3+2)
Pitfall 2: Forgetting Edge Cases with Very Short Strings
The Mistake: Not handling strings that are too short to form the required pattern:
# Missing edge case check
def isDecomposable(self, s: str) -> bool:
# Forgot to check if string is even long enough
# Minimum valid length is 5 (one pair + one triplet)
i = 0
n = len(s)
pair_count = 0
# ... rest of code
The Solution: While the modulo logic naturally handles this (a string of length < 5 will either have remainder 1 or won't have exactly one pair), explicitly checking can make the code clearer:
def isDecomposable(self, s: str) -> bool:
n = len(s)
# Early return for strings too short to be valid
if n < 5:
return False
# Continue with main logic...
Pitfall 3: Incorrect Pair Counting Logic
The Mistake: Counting pairs incorrectly when a group can contribute multiple pairs:
# INCORRECT: Thinking each group can only contribute one pair maximum if group_length % 3 == 2: pair_count += 1 # What if group_length is 8? (8 = 3 + 3 + 2)
Why The Given Solution Works: The solution correctly handles this because:
- A group with length % 3 == 2 contributes exactly ONE pair regardless of total length
- For example: length 5 → 3+2 (one pair), length 8 → 3+3+2 (one pair), length 11 → 3+3+3+2 (one pair)
- The algorithm correctly counts this as one pair contribution per group
Pitfall 4: Off-by-One Errors in Group Detection
The Mistake: Incorrectly identifying group boundaries:
# INCORRECT: Off-by-one error while i < n: j = i + 1 # Starting j at i+1 instead of i while j < n and s[j] == s[i]: j += 1 group_length = j - i # This would be wrong if j started at i+1
The Correct Approach: Always start j
at the same position as i
to correctly count the group length:
j = i # Start at the same position while j < n and s[j] == s[i]: j += 1 group_length = j - i # Correct length calculation
How does merge sort divide the problem into subproblems?
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 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
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 represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!