Count the Number of Substrings With Dominant Ones
MedPrompt
You are given a binary string s.
Return the number of substrings with dominant ones.
A string has dominant ones if the number of ones in the string is greater than or equal to the square of the number of zeros in the string.
Example 1:
Input: s = "00011"
Output: 5
Explanation:
The substrings with dominant ones are shown in the table below.
| i | j | s[i..j] | Number of Zeros | Number of Ones |
|---|---|---|---|---|
| 3 | 3 | 1 | 0 | 1 |
| 4 | 4 | 1 | 0 | 1 |
| 2 | 3 | 01 | 1 | 1 |
| 3 | 4 | 11 | 0 | 2 |
| 2 | 4 | 011 | 1 | 2 |
Example 2:
Input: s = "101101"
Output: 16
Explanation:
The substrings with non-dominant ones are shown in the table below.
Since there are 21 substrings total and 5 of them have non-dominant ones, it follows that there are 16 substrings with dominant ones.
| i | j | s[i..j] | Number of Zeros | Number of Ones |
|---|---|---|---|---|
| 1 | 1 | 0 | 1 | 0 |
| 4 | 4 | 0 | 1 | 0 |
| 1 | 4 | 0110 | 2 | 2 |
| 0 | 4 | 10110 | 2 | 3 |
| 1 | 5 | 01101 | 2 | 3 |
Constraints:
1 <= s.length <= 4 * 104sconsists only of characters'0'and'1'.
Approaches
2 approaches with complexity analysis and trade-offs.
The most straightforward approach is to generate every possible substring of the input string s, and for each substring, check if it satisfies the 'dominant ones' condition. A substring is dominant if the count of '1's is greater than or equal to the square of the count of '0's.
Algorithm
- Initialize a variable
ansto 0 to store the count of dominant substrings. - Use a nested loop to iterate through all possible substrings. The outer loop variable
irepresents the start of the substring, and the inner loop variablejrepresents the end. - For each starting index
i, initialize counts for zeros and ones to 0. - In the inner loop, for each character
s[j], update the counts of zeros and ones for the substrings[i..j]. - Check if the condition
ones >= zeros * zerosis met. - If the condition is true, increment
ans. - After iterating through all substrings, return
ans.
Walkthrough
We can use two nested loops to define all substrings. The outer loop iterates through all possible starting indices i from 0 to n-1, and the inner loop iterates through all possible ending indices j from i to n-1, where n is the length of the string.
For each substring s[i..j], we can maintain a running count of zeros (zeros) and ones (ones). As we extend the substring by moving j from i to n-1, we update these counts. After each update, we check if the condition ones >= zeros * zeros holds. If it does, we increment our total count of dominant substrings.
Here is the algorithm:
- Initialize a counter
ansto 0. - Loop for
ifrom0ton-1: a. Initializezeros = 0andones = 0. b. Loop forjfromiton-1: i. Ifs[j]is '0', incrementzeros. Otherwise, incrementones. ii. Check ifones >= zeros * zeros. iii. If the condition is true, incrementans. - Return
ans.
class Solution { public long countSubstrings(String s) { int n = s.length(); long ans = 0; for (int i = 0; i < n; i++) { int zeros = 0; int ones = 0; for (int j = i; j < n; j++) { if (s.charAt(j) == '0') { zeros++; } else { ones++; } if (ones >= (long)zeros * zeros) { ans++; } } } return ans; }}Complexity
Time
O(N^2), where N is the length of the string `s`. The two nested loops iterate through all possible substrings, which are O(N^2) in number. For each substring, the check is an O(1) operation.
Space
O(1), as we only use a few variables to store the counts and the final answer, regardless of the input string size.
Trade-offs
Pros
Simple to understand and implement.
Cons
Inefficient for large inputs. Given N can be up to 4 * 10^4, N^2 can be up to 1.6 * 10^9, which will lead to a Time Limit Exceeded (TLE) error.
A key observation is that the number of zeros (c0) in a dominant substring cannot be very large. The condition is c1 >= c0 * c0. Since the number of ones c1 is at most the length of the string N, we must have N >= c1 >= c0 * c0, which implies c0 <= sqrt(N). This insight allows us to design a more efficient algorithm by limiting our search based on the number of zeros.
Instead of a simple nested loop, we can iterate through each possible starting position i and then, for each i, count the valid substrings based on the number of zeros they contain.
Algorithm
- Pre-process the string to get a list of all indices where '0' appears. Let's call it
zeroIndices. - Initialize a total count
ansto 0. - Iterate through the string with an index
ifrom 0 ton-1, representing the start of a substring. - For each
i, find the indexzeroPtrinzeroIndicescorresponding to the first '0' at or afteri. - First, count substrings with zero '0's. These are substrings starting at
iand ending before the '0' atzeroIndices[zeroPtr]. All such non-empty substrings are dominant. Add their count toans. - Then, iterate for the number of zeros
c0from 1 up tosqrt(n). We can stop atsqrt(n)because ifc0 > sqrt(n), thenc0*c0 > n, making the conditionc1 >= c0*c0impossible to satisfy asc1 <= n. - For each
c0, determine the range of ending indicesjsuch thats[i..j]has exactlyc0zeros. This range is bounded by thec0-th and(c0+1)-th zeros fromzeroPtr. - Calculate the minimum
jrequired by the dominance condition:j >= i + c0*c0 + c0 - 1. - The number of valid substrings for this
iandc0is the size of the intersection of the two ranges forj. Add this number toans. - Return
ansafter the outer loop finishes.
Walkthrough
The algorithm iterates through each index i as a potential start of a substring. For each i, we calculate the number of dominant substrings starting at i.
We can handle two cases for the number of zeros (c0) in a substring s[i..j]:
c0 = 0: The substring contains only '1's. The conditionc1 >= 0*0is always true for non-empty substrings. We can find the next occurrence of '0' afteri(or the end of the string) and add all substringss[i..j]within this range to the count.c0 > 0: We can iterate through the possible number of zeros,c0, from 1 up tosqrt(N). For a fixediandc0, we can determine the exact range of ending indicesjfor which the substrings[i..j]contains exactlyc0zeros. This range is defined by the positions of thec0-th and(c0+1)-th zeros after indexi. Within this range ofj, we apply the dominance conditionc1 >= c0*c0, which simplifies to a condition onj:j >= i + c0*c0 + c0 - 1. We can then count how many integersjfall into the intersection of these two ranges in O(1) time.
To efficiently find the positions of zeros, we can pre-process the string and store the indices of all zeros in a list.
Here is the algorithm:
- Pre-compute a list
zeroIndicescontaining the indices of all '0's ins. - Initialize
ans = 0. - Iterate
ifrom0ton-1: a. Find the indexzeroPtrof the first zero inzeroIndicesthat is at or afteri. b. Casec0 = 0: All substrings starting atiand ending before the zero atzeroIndices[zeroPtr]are dominant. Add their count toans. c. Casec0 > 0: Loopc0from 1 as long asc0*c0 <= n. i. Determine the range ofjwheres[i..j]has exactlyc0zeros. This is from thec0-th zero afteriup to the character before the(c0+1)-th zero. ii. Calculate the minimumjrequired by the dominance condition:minJ = i + c0*c0 + c0 - 1. iii. Find the number ofj's that satisfy both conditions and add this toans. - Return
ans.
import java.util.ArrayList;import java.util.List; class Solution { public long countSubstrings(String s) { int n = s.length(); long count = 0; List<Integer> zeroIndices = new ArrayList<>(); for (int i = 0; i < n; i++) { if (s.charAt(i) == '0') { zeroIndices.add(i); } } int numZeros = zeroIndices.size(); int zeroPtr = 0; for (int i = 0; i < n; i++) { // Find the first zero at or after index i while (zeroPtr < numZeros && zeroIndices.get(zeroPtr) < i) { zeroPtr++; } // Case 1: Substrings with c0 = 0 zeros int nextZeroPos = (zeroPtr < numZeros) ? zeroIndices.get(zeroPtr) : n; // All substrings s[i...j] where j is in [i, nextZeroPos - 1] have 0 zeros. // They are all dominant. There are (nextZeroPos - i) such substrings. count += (nextZeroPos - i); // Case 2: Substrings with c0 > 0 zeros for (int c0 = 1; (long)c0 * c0 <= n; c0++) { int currentZeroGroupIdx = zeroPtr + c0 - 1; if (currentZeroGroupIdx >= numZeros) { break; // Not enough zeros left in the string } // A substring s[i..j] with c0 zeros must end at or after the c0-th zero from i // and before the (c0+1)-th zero. int startJRange = zeroIndices.get(currentZeroGroupIdx); int endJRange = (currentZeroGroupIdx + 1 < numZeros) ? zeroIndices.get(currentZeroGroupIdx + 1) - 1 : n - 1; // Dominance condition: c1 >= c0*c0 => (j-i+1) - c0 >= c0*c0 // => j >= i + c0*c0 + c0 - 1 long minJ = (long)i + (long)c0 * c0 + (long)c0 - 1; long validStartJ = Math.max(startJRange, minJ); long validEndJ = endJRange; if (validStartJ <= validEndJ) { count += (validEndJ - validStartJ + 1); } } } return count; }}Complexity
Time
O(N * sqrt(N)), where N is the length of the string. The outer loop runs N times. The inner loop over `c0` runs at most `sqrt(N)` times. All operations inside the inner loop take O(1) time (assuming pre-computation). The pre-computation of zero indices takes O(N).
Space
O(Z), where Z is the number of zeros in the string, to store `zeroIndices`. In the worst case, Z can be N, so the space complexity is O(N).
Trade-offs
Pros
Significantly more efficient than the brute-force approach.
Passes the time limits for the given constraints.
Cons
More complex to reason about and implement correctly compared to the brute-force solution.
Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.