2730. Find the Longest Semi-Repetitive Substring
Problem Description
You are given a string s
that contains only digits from 0 to 9.
A string is called semi-repetitive if it contains at most one adjacent pair of identical digits. In other words, there can be at most one place where two consecutive characters are the same.
Examples of semi-repetitive strings:
"0010"
- has one adjacent pair (00)"002020"
- has one adjacent pair (00)"0123"
- has no adjacent pairs"2002"
- has one adjacent pair (00)"54944"
- has one adjacent pair (44)
Examples of strings that are NOT semi-repetitive:
"00101022"
- has two adjacent pairs (00 and 22)"1101234883"
- has two adjacent pairs (11 and 88)
Your task is to find the length of the longest substring of s
that is semi-repetitive. A substring is a contiguous sequence of characters within the string.
For example, if s = "52233"
, the longest semi-repetitive substring would be "5223"
(which has only one adjacent pair: 22), giving a length of 4.
The solution uses a sliding window approach with two pointers (j
and i
) to maintain a valid window. The variable cnt
tracks how many adjacent pairs of identical digits exist in the current window. When cnt
exceeds 1, the left pointer j
is moved right until the window becomes valid again (having at most one adjacent pair). The maximum window size encountered is tracked and returned as the answer.
Intuition
The key insight is recognizing that this is a sliding window problem. We need to find the longest contiguous portion of the string that satisfies a specific constraint - having at most one pair of adjacent identical digits.
Think of it like this: we want to expand our window as much as possible while keeping it valid. A window is valid when it contains at most one pair of consecutive identical digits.
We can start with two pointers at the beginning of the string and expand the right pointer. As we expand, we keep track of how many pairs of adjacent identical digits we have in our current window.
The moment we encounter more than one pair (making our window invalid), we need to shrink from the left. We keep shrinking until we remove one of the pairs, making our window valid again.
Why does this work? Because:
- We want the longest substring, so we should try to expand our window as much as possible
- When we have too many pairs (more than 1), the only way to fix it is to remove characters from the beginning of our window
- We don't need to check all possible substrings - the sliding window naturally explores all potentially optimal solutions
The counting mechanism is clever: we increment cnt
when we find an adjacent pair while expanding right, and decrement cnt
when we pass over an adjacent pair while shrinking from the left. This efficiently tracks the number of adjacent pairs in our current window without having to rescan the entire window each time.
This approach gives us an optimal O(n)
time complexity since each character is visited at most twice - once by the right pointer and once by the left pointer.
Learn more about Sliding Window patterns.
Solution Approach
We implement a two-pointer sliding window approach to find the longest semi-repetitive substring.
Variables Setup:
j = 0
: Left pointer of our sliding windowi
: Right pointer that iterates through the string starting from index 1cnt = 0
: Counter for the number of adjacent pairs of identical digits in our current windowans = 1
: Stores the maximum length found (initialized to 1 since any single character is semi-repetitive)
Algorithm Steps:
-
Expand the window: We iterate through the string with pointer
i
starting from index 1:for i in range(1, n):
-
Count adjacent pairs: When we include a new character at position
i
, we check if it forms a pair with the previous character:cnt += s[i] == s[i - 1]
If
s[i] == s[i - 1]
is true, we've found an adjacent pair and incrementcnt
. -
Shrink if invalid: When
cnt > 1
(more than one adjacent pair), we need to shrink from the left:while cnt > 1: cnt -= s[j] == s[j + 1] j += 1
- Before moving
j
forward, we check if positionsj
andj + 1
form an adjacent pair - If they do, we decrement
cnt
since we're removing this pair from our window - Move
j
forward to shrink the window
- Before moving
-
Update maximum length: After ensuring our window is valid (at most 1 adjacent pair):
ans = max(ans, i - j + 1)
The current window size is
i - j + 1
.
Why this works:
- The window
s[j..i]
always maintains the invariant of having at most one adjacent pair - By only moving
j
when necessary (whencnt > 1
), we ensure we're always considering the longest possible valid substring ending at positioni
- The algorithm explores all possible valid substrings efficiently in linear time
Time Complexity: O(n)
where n is the length of the string, as each character is visited at most twice.
Space Complexity: O(1)
as we only use a constant amount of extra space.
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 algorithm with s = "52233"
.
Initial State:
n = 5
,j = 0
,cnt = 0
,ans = 1
- Window: empty initially
Step 1: i = 1
- Check if
s[1] == s[0]
→'2' == '5'
→ False cnt
remains 0 (no adjacent pairs)- Window:
[j=0, i=1]
→"52"
- Update
ans = max(1, 1-0+1) = 2
Step 2: i = 2
- Check if
s[2] == s[1]
→'2' == '2'
→ True cnt = 1
(found one adjacent pair: 22)cnt ≤ 1
, so no shrinking needed- Window:
[j=0, i=2]
→"522"
- Update
ans = max(2, 2-0+1) = 3
Step 3: i = 3
- Check if
s[3] == s[2]
→'3' == '2'
→ False cnt
remains 1- Window:
[j=0, i=3]
→"5223"
- Update
ans = max(3, 3-0+1) = 4
Step 4: i = 4
- Check if
s[4] == s[3]
→'3' == '3'
→ True cnt = 2
(now have two adjacent pairs: 22 and 33)- Need to shrink!
cnt > 1
:- Check if
s[0] == s[1]
→'5' == '2'
→ False cnt
remains 2- Move
j = 1
- Still
cnt > 1
, continue shrinking: - Check if
s[1] == s[2]
→'2' == '2'
→ True cnt = 1
(removed the 22 pair)- Move
j = 2
- Now
cnt = 1
, stop shrinking
- Check if
- Window:
[j=2, i=4]
→"233"
- Update
ans = max(4, 4-2+1) = 4
Final Answer: 4
The longest semi-repetitive substring is "5223"
with length 4, containing only one adjacent pair (22).
Solution Implementation
1class Solution:
2 def longestSemiRepetitiveSubstring(self, s: str) -> int:
3 # Initialize the maximum length to 1 (single character is always valid)
4 max_length = 1
5 string_length = len(s)
6
7 # Count of consecutive pairs in current window
8 consecutive_pairs_count = 0
9
10 # Left pointer of the sliding window
11 left = 0
12
13 # Iterate through the string starting from index 1
14 for right in range(1, string_length):
15 # Check if current character equals previous character
16 if s[right] == s[right - 1]:
17 consecutive_pairs_count += 1
18
19 # If we have more than 1 consecutive pair, shrink window from left
20 while consecutive_pairs_count > 1:
21 # Check if removing left element reduces consecutive pairs
22 if s[left] == s[left + 1]:
23 consecutive_pairs_count -= 1
24 left += 1
25
26 # Update maximum length with current window size
27 current_window_length = right - left + 1
28 max_length = max(max_length, current_window_length)
29
30 return max_length
31
1class Solution {
2 public int longestSemiRepetitiveSubstring(String s) {
3 // Initialize result with minimum possible length of 1
4 int maxLength = 1;
5 int n = s.length();
6
7 // Use sliding window approach with two pointers
8 int left = 0; // Left pointer of the window
9 int adjacentPairCount = 0; // Count of adjacent equal character pairs in current window
10
11 // Iterate through string with right pointer
12 for (int right = 1; right < n; right++) {
13 // Check if current character equals previous character
14 // If yes, increment the count of adjacent equal pairs
15 if (s.charAt(right) == s.charAt(right - 1)) {
16 adjacentPairCount++;
17 }
18
19 // Shrink window from left while we have more than 1 adjacent equal pair
20 // (semi-repetitive substring allows at most 1 adjacent equal pair)
21 while (adjacentPairCount > 1) {
22 // Check if character at left pointer forms an equal pair with next character
23 // If yes, decrement the count as we're removing this pair from window
24 if (s.charAt(left) == s.charAt(left + 1)) {
25 adjacentPairCount--;
26 }
27 left++; // Move left pointer to shrink window
28 }
29
30 // Update maximum length found so far
31 // Window size is (right - left + 1)
32 maxLength = Math.max(maxLength, right - left + 1);
33 }
34
35 return maxLength;
36 }
37}
38
1class Solution {
2public:
3 int longestSemiRepetitiveSubstring(string s) {
4 int maxLength = 1; // Initialize maximum length to 1 (single character is always valid)
5 int n = s.size(); // Store string length for efficiency
6
7 // Use sliding window technique with two pointers
8 int left = 0; // Left boundary of the sliding window
9 int pairCount = 0; // Count of adjacent equal character pairs in current window
10
11 // Iterate through string with right pointer
12 for (int right = 1; right < n; ++right) {
13 // Check if current character equals previous character
14 // If yes, increment the count of adjacent equal pairs
15 if (s[right] == s[right - 1]) {
16 pairCount++;
17 }
18
19 // If we have more than 1 adjacent equal pair, shrink window from left
20 while (pairCount > 1) {
21 // Before moving left pointer, check if we're removing an equal pair
22 if (s[left] == s[left + 1]) {
23 pairCount--;
24 }
25 left++; // Move left boundary to shrink the window
26 }
27
28 // Update maximum length with current valid window size
29 maxLength = max(maxLength, right - left + 1);
30 }
31
32 return maxLength;
33 }
34};
35
1/**
2 * Finds the length of the longest semi-repetitive substring.
3 * A semi-repetitive substring is one that contains at most one pair of consecutive identical characters.
4 *
5 * @param s - The input string to analyze
6 * @returns The length of the longest semi-repetitive substring
7 */
8function longestSemiRepetitiveSubstring(s: string): number {
9 const stringLength: number = s.length;
10 let maxLength: number = 1;
11
12 // Use sliding window approach with two pointers
13 let leftPointer: number = 0;
14 let consecutivePairCount: number = 0;
15
16 for (let rightPointer: number = 1; rightPointer < stringLength; rightPointer++) {
17 // Check if current character is same as previous character
18 if (s[rightPointer] === s[rightPointer - 1]) {
19 consecutivePairCount++;
20 }
21
22 // Shrink window from left if we have more than one consecutive pair
23 while (consecutivePairCount > 1) {
24 // Check if the character at left pointer forms a consecutive pair with next character
25 if (s[leftPointer] === s[leftPointer + 1]) {
26 consecutivePairCount--;
27 }
28 leftPointer++;
29 }
30
31 // Update maximum length found so far
32 const currentWindowLength: number = rightPointer - leftPointer + 1;
33 maxLength = Math.max(maxLength, currentWindowLength);
34 }
35
36 return maxLength;
37}
38
Time and Space Complexity
Time Complexity: O(n)
, where n
is the length of the string s
.
The algorithm uses a two-pointer sliding window approach. The outer loop iterates through each character from index 1 to n-1, which takes O(n)
time. The inner while loop might seem to add complexity, but each element can only be visited by the j
pointer at most once throughout the entire execution. The j
pointer only moves forward and never resets, moving from 0 to at most n-1 across all iterations. Therefore, the total number of operations is bounded by 2n
(each element is visited at most twice - once by i
and once by j
), which simplifies to O(n)
.
Space Complexity: O(1)
The algorithm only uses a constant amount of extra space. The variables ans
, n
, cnt
, j
, and i
are all single integer values that don't scale with the input size. No additional data structures like arrays, lists, or hash maps are created. The space used remains constant regardless of the input string length.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Incorrect Initialization of Answer Variable
The Problem: A common mistake is initializing the answer variable to 0 instead of 1. This causes issues when the input string has length 1.
Incorrect Code:
max_length = 0 # Wrong initialization!
Why it fails:
- For a single-character string like
"5"
, the loop never executes (since we start from index 1) - The function returns 0, but the correct answer should be 1
- Any single character is a valid semi-repetitive substring
Correct Solution:
max_length = 1 # Correct initialization
Pitfall 2: Off-by-One Error When Checking Adjacent Pairs
The Problem: When shrinking the window, developers often confuse which indices to check for forming an adjacent pair.
Incorrect Code:
while consecutive_pairs_count > 1: if s[left - 1] == s[left]: # Wrong indices! consecutive_pairs_count -= 1 left += 1
Why it fails:
- When
left = 0
, accessings[left - 1]
causes an index out of bounds error - Even if it doesn't crash, it's checking the wrong pair - we need to check if removing the character at
left
breaks a pair with its successor
Correct Solution:
while consecutive_pairs_count > 1: if s[left] == s[left + 1]: # Check left with its successor consecutive_pairs_count -= 1 left += 1
Pitfall 3: Forgetting to Update Answer Inside the Loop
The Problem: Some implementations only update the answer after the loop completes, missing optimal windows during iteration.
Incorrect Code:
for right in range(1, string_length):
if s[right] == s[right - 1]:
consecutive_pairs_count += 1
while consecutive_pairs_count > 1:
if s[left] == s[left + 1]:
consecutive_pairs_count -= 1
left += 1
# Only updating answer once at the end - Wrong!
return right - left + 1
Why it fails:
- The final window might not be the longest valid window
- For example, with input
"52233"
, the algorithm might end with a smaller window than the maximum encountered during processing
Correct Solution:
for right in range(1, string_length):
# ... window adjustment logic ...
# Update answer at each iteration
current_window_length = right - left + 1
max_length = max(max_length, current_window_length)
Pitfall 4: Not Handling Edge Cases Properly
The Problem: Failing to handle edge cases like empty strings or strings with all identical characters.
Scenarios to consider:
- Empty string (though typically problem constraints specify minimum length)
- String of length 1
- String with all identical characters like
"1111"
- String with no adjacent pairs like
"12345"
Robust Solution:
def longestSemiRepetitiveSubstring(self, s: str) -> int:
# Handle edge case
if len(s) <= 1:
return len(s)
# Rest of the implementation...
Which of the following is a min heap?
Recommended Readings
https assets algo monster cover_photos stack svg Sliding Window Maximum Monotonic Stack We have an array and a sliding window defined by a start index and an end index The sliding window moves from left of the array to right There are always k elements in the window The window
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
Want a Structured Path to Master System Design Too? Don’t Miss This!