3258. Count Substrings That Satisfy K-Constraint I
Problem Description
You are given a binary string s
(containing only '0's and '1's) and an integer k
.
A binary string satisfies the k-constraint if at least one of these conditions is true:
- The string contains at most
k
zeros (0's) - The string contains at most
k
ones (1's)
Your task is to count how many substrings of s
satisfy the k-constraint.
For example, if s = "10101"
and k = 1
:
- Substring "1" satisfies the k-constraint (0 zeros, 1 one)
- Substring "0" satisfies the k-constraint (1 zero, 0 ones)
- Substring "10" satisfies the k-constraint (1 zero, 1 one)
- Substring "101" does NOT satisfy the k-constraint (1 zero, 2 ones - both counts exceed k)
The solution uses a sliding window approach. It maintains a window [l, r]
and tracks the count of 0's and 1's in the current window using cnt[0]
and cnt[1]
. For each position r
, it:
- Adds the character at position
r
to the window - Shrinks the window from the left (incrementing
l
) while both counts exceedk
- Once the window satisfies the k-constraint, all substrings ending at position
r
and starting from any position betweenl
andr
are valid, contributingr - l + 1
substrings to the answer
The algorithm efficiently counts all valid substrings in O(n)
time by recognizing that if a window [l, r]
satisfies the k-constraint, then all its suffixes ending at r
also satisfy the constraint.
Intuition
The key insight is recognizing that if a substring satisfies the k-constraint, then all of its substrings also satisfy the k-constraint. Conversely, if a substring violates the k-constraint (having more than k
zeros AND more than k
ones), then any substring containing it will also violate the constraint.
This property suggests using a sliding window approach. We can maintain a window that always satisfies the k-constraint and count all valid substrings ending at each position.
Consider what happens as we expand our window to the right by including a new character:
- If the window still satisfies the k-constraint, great! We've found more valid substrings.
- If the window now violates the k-constraint (both counts exceed
k
), we need to shrink from the left until it's valid again.
The clever counting trick comes from realizing that for a valid window [l, r]
, we don't need to check every possible substring individually. Instead, we know that all substrings ending at position r
and starting anywhere from l
to r
are valid. That gives us exactly r - l + 1
valid substrings ending at position r
.
Why does this work? Because if the entire window [l, r]
satisfies the k-constraint (at least one count ≤ k
), then any smaller window within it will have even smaller counts, thus also satisfying the constraint.
This approach avoids the naive O(n²)
or O(n³)
solution of checking every possible substring. By maintaining a valid window and using the counting trick, we process each character exactly once, achieving O(n)
time complexity.
Learn more about Sliding Window patterns.
Solution Approach
The solution implements a sliding window technique with two pointers to efficiently count all valid substrings.
Data Structures Used:
cnt = [0, 0]
: An array to track the count of 0's and 1's in the current window.cnt[0]
stores the count of 0's, andcnt[1]
stores the count of 1's.l
: Left pointer of the sliding window, initially at position 0.ans
: Accumulator for the total count of valid substrings.
Algorithm Steps:
-
Initialize the window: Start with an empty window where
l = 0
and counts are[0, 0]
. -
Expand the window: Iterate through the string with the right pointer
r
. For each character:- Convert the character to an integer (
'0'
→ 0,'1'
→ 1) - Increment the corresponding count:
cnt[x] += 1
- Convert the character to an integer (
-
Shrink if necessary: After adding a new character, check if the window violates the k-constraint:
while cnt[0] > k and cnt[1] > k: cnt[int(s[l])] -= 1 l += 1
This loop continues shrinking the window from the left until at least one count becomes ≤
k
. -
Count valid substrings: Once the window
[l, r]
is valid, all substrings ending at positionr
are valid. The number of such substrings isr - l + 1
:- Substring from position
l
tor
(length:r - l + 1
) - Substring from position
l + 1
tor
(length:r - l
) - ...
- Substring from position
r
tor
(length: 1)
- Substring from position
-
Accumulate the result: Add
r - l + 1
toans
for each valid window state.
Time Complexity: O(n)
where n is the length of the string. Each character is visited at most twice (once by the right pointer, once by the left pointer).
Space Complexity: O(1)
as we only use a fixed amount of extra space regardless of input size.
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 trace through the algorithm with s = "11010"
and k = 1
.
Initial State:
l = 0
,ans = 0
,cnt = [0, 0]
(counts for 0's and 1's)
Step 1: r = 0, character = '1'
- Add '1' to window:
cnt = [0, 1]
- Check constraint:
cnt[0] = 0 ≤ 1
✓ (satisfies k-constraint) - Valid substrings ending at position 0:
r - l + 1 = 0 - 0 + 1 = 1
- "1" (from index 0 to 0)
ans = 0 + 1 = 1
Step 2: r = 1, character = '1'
- Add '1' to window:
cnt = [0, 2]
- Check constraint:
cnt[0] = 0 ≤ 1
✓ (satisfies k-constraint) - Valid substrings ending at position 1:
r - l + 1 = 1 - 0 + 1 = 2
- "11" (from index 0 to 1)
- "1" (from index 1 to 1)
ans = 1 + 2 = 3
Step 3: r = 2, character = '0'
- Add '0' to window:
cnt = [1, 2]
- Check constraint:
cnt[0] = 1 ≤ 1
✓ (satisfies k-constraint) - Valid substrings ending at position 2:
r - l + 1 = 2 - 0 + 1 = 3
- "110" (from index 0 to 2)
- "10" (from index 1 to 2)
- "0" (from index 2 to 2)
ans = 3 + 3 = 6
Step 4: r = 3, character = '1'
- Add '1' to window:
cnt = [1, 3]
- Check constraint:
cnt[0] = 1 ≤ 1
✓ (satisfies k-constraint) - Valid substrings ending at position 3:
r - l + 1 = 3 - 0 + 1 = 4
- "1101" (from index 0 to 3)
- "101" (from index 1 to 3)
- "01" (from index 2 to 3)
- "1" (from index 3 to 3)
ans = 6 + 4 = 10
Step 5: r = 4, character = '0'
- Add '0' to window:
cnt = [2, 3]
- Check constraint: Both
cnt[0] = 2 > 1
andcnt[1] = 3 > 1
✗ (violates k-constraint) - Shrink window:
- Remove s[0] = '1':
cnt = [2, 2]
,l = 1
- Still violates (both > 1), continue shrinking
- Remove s[1] = '1':
cnt = [2, 1]
,l = 2
- Now
cnt[1] = 1 ≤ 1
✓ (satisfies k-constraint)
- Remove s[0] = '1':
- Valid substrings ending at position 4:
r - l + 1 = 4 - 2 + 1 = 3
- "010" (from index 2 to 4)
- "10" (from index 3 to 4)
- "0" (from index 4 to 4)
ans = 10 + 3 = 13
Final Answer: 13
The algorithm efficiently counted all 13 valid substrings by maintaining a sliding window that always satisfies the k-constraint, avoiding the need to check each substring individually.
Solution Implementation
1class Solution:
2 def countKConstraintSubstrings(self, s: str, k: int) -> int:
3 # Track count of 0s and 1s in current window
4 count = [0, 0] # count[0] for '0's, count[1] for '1's
5
6 # Initialize result and left pointer for sliding window
7 result = 0
8 left = 0
9
10 # Iterate through string with right pointer
11 for right in range(len(s)):
12 # Convert character to integer (0 or 1) and update count
13 digit = int(s[right])
14 count[digit] += 1
15
16 # Shrink window from left while both 0s and 1s exceed k
17 while count[0] > k and count[1] > k:
18 left_digit = int(s[left])
19 count[left_digit] -= 1
20 left += 1
21
22 # Add number of valid substrings ending at current position
23 # All substrings from left to right are valid
24 result += right - left + 1
25
26 return result
27
1class Solution {
2 public int countKConstraintSubstrings(String s, int k) {
3 // Array to count occurrences of '0' and '1'
4 // count[0] stores count of '0's, count[1] stores count of '1's
5 int[] count = new int[2];
6
7 // Total number of valid substrings
8 int totalSubstrings = 0;
9
10 // Left pointer for sliding window
11 int left = 0;
12
13 // Iterate through string with right pointer
14 for (int right = 0; right < s.length(); right++) {
15 // Increment count for current character ('0' or '1')
16 // s.charAt(right) - '0' converts char to int (0 or 1)
17 count[s.charAt(right) - '0']++;
18
19 // Shrink window from left while both counts exceed k
20 // Window is invalid when both '0's and '1's count > k
21 while (count[0] > k && count[1] > k) {
22 // Decrement count for character at left pointer and move left forward
23 count[s.charAt(left) - '0']--;
24 left++;
25 }
26
27 // Add count of all valid substrings ending at current right position
28 // Number of substrings = right - left + 1
29 totalSubstrings += right - left + 1;
30 }
31
32 return totalSubstrings;
33 }
34}
35
1class Solution {
2public:
3 int countKConstraintSubstrings(string s, int k) {
4 // Array to count occurrences of '0' and '1' in the current window
5 // count[0] stores count of '0's, count[1] stores count of '1's
6 int count[2] = {0, 0};
7
8 // Total number of valid substrings
9 int result = 0;
10
11 // Left pointer of the sliding window
12 int left = 0;
13
14 // Iterate through the string with right pointer
15 for (int right = 0; right < s.length(); ++right) {
16 // Add current character to the window
17 // s[right] - '0' converts '0' to 0 and '1' to 1
18 count[s[right] - '0']++;
19
20 // Shrink window from left while both counts exceed k
21 // (violating the k-constraint)
22 while (count[0] > k && count[1] > k) {
23 // Remove leftmost character from window and move left pointer
24 count[s[left] - '0']--;
25 left++;
26 }
27
28 // All substrings ending at 'right' and starting from
29 // any position between 'left' and 'right' are valid
30 // Number of such substrings = right - left + 1
31 result += right - left + 1;
32 }
33
34 return result;
35 }
36};
37
1/**
2 * Counts the number of substrings where either the count of '0's or '1's is at most k
3 * @param s - Binary string containing only '0's and '1's
4 * @param k - Maximum allowed count for either '0's or '1's in valid substrings
5 * @returns The total number of valid substrings
6 */
7function countKConstraintSubstrings(s: string, k: number): number {
8 // Array to track count of '0's at index 0 and '1's at index 1
9 const digitCount: [number, number] = [0, 0];
10
11 // totalSubstrings: accumulates the count of valid substrings
12 // leftPointer: maintains the left boundary of the sliding window
13 let totalSubstrings: number = 0;
14 let leftPointer: number = 0;
15
16 // Iterate through string with rightPointer as the right boundary of the window
17 for (let rightPointer: number = 0; rightPointer < s.length; rightPointer++) {
18 // Increment count for the current digit (converts '0'/'1' to 0/1 using unary +)
19 digitCount[+s[rightPointer]]++;
20
21 // Shrink window from left while both '0's and '1's exceed k
22 while (digitCount[0] > k && digitCount[1] > k) {
23 // Decrement count for the digit being removed from window
24 digitCount[+s[leftPointer]]--;
25 leftPointer++;
26 }
27
28 // Add count of all valid substrings ending at rightPointer
29 // The number of such substrings equals the window size
30 totalSubstrings += rightPointer - leftPointer + 1;
31 }
32
33 return totalSubstrings;
34}
35
Time and Space Complexity
Time Complexity: O(n)
, where n
is the length of the string s
.
The algorithm uses a sliding window approach with two pointers (l
and r
). The right pointer r
iterates through each character in the string exactly once in the for loop. The left pointer l
moves forward in the while loop, but each position in the string is visited by l
at most once throughout the entire execution. Since each element is processed at most twice (once by r
and once by l
), and all operations inside the loops (array access, addition, subtraction) are O(1)
, the overall time complexity is O(n)
.
Space Complexity: O(1)
The algorithm uses a fixed amount of extra space:
cnt
: an array of size 2 to track the count of 0s and 1s in the current windowans
,l
,r
,x
: integer variables for storing the result and pointers
Since the space usage doesn't grow with the input size and remains constant regardless of the length of string s
, the space complexity is O(1)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Misunderstanding the Window Validity Contribution
The Mistake:
A common error is thinking that when we have a valid window [l, r]
, we should only count it as one valid substring. This leads to incorrect code like:
# INCORRECT approach
for right in range(len(s)):
count[int(s[right])] += 1
while count[0] > k and count[1] > k:
count[int(s[left])] -= 1
left += 1
result += 1 # Wrong! Only counts one substring per position
Why It's Wrong:
When the window [l, r]
satisfies the k-constraint, ALL substrings ending at position r
are valid. This includes:
- The substring from
l
tor
- The substring from
l+1
tor
- The substring from
l+2
tor
- ... up to the substring from
r
tor
(single character)
The Fix:
Add right - left + 1
to the result, which counts all valid substrings ending at the current position:
result += right - left + 1 # Correct: counts all valid substrings ending at r
Pitfall 2: Incorrect Window Shrinking Condition
The Mistake: Using OR instead of AND in the shrinking condition:
# INCORRECT: shrinks too aggressively
while count[0] > k or count[1] > k:
count[int(s[left])] -= 1
left += 1
Why It's Wrong: The k-constraint is satisfied when EITHER the count of 0s is ≤ k OR the count of 1s is ≤ k. We only need to shrink when BOTH counts exceed k. Using OR would shrink the window even when it's still valid, missing many valid substrings.
Example:
For s = "110"
and k = 1
:
- After processing "11", we have
count = [0, 2]
- With OR condition: window shrinks (since
count[1] > k
) - With AND condition: window doesn't shrink (since
count[0] ≤ k
) - The substring "11" is actually valid because it has 0 zeros (≤ k)
The Fix: Use AND to ensure we only shrink when both conditions are violated:
while count[0] > k and count[1] > k: # Correct: only shrink when both exceed k
Pitfall 3: Off-by-One Error in Counting
The Mistake:
Using right - left
instead of right - left + 1
:
# INCORRECT: misses counting some substrings result += right - left
Why It's Wrong:
The number of substrings ending at position r
and starting anywhere from l
to r
is r - l + 1
, not r - l
. For example, if l = 2
and r = 4
, there are 3 valid substrings: [2,4]
, [3,4]
, and [4,4]
.
The Fix:
Always add 1 to account for all positions from l
to r
inclusive:
result += right - left + 1 # Correct formula
A heap is a ...?
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!