This can be solved in O(n) time and space using the "net" values computed by this answer in combination with the following observation:
- A substring s[i .. j] has the same number of consonants and vowels if and only if net[1 .. i-1] = net[1 .. j], where net[i .. j] is the sum of the "net" values (1 for a vowel, -1 for a consonant) for each character between positions i and j, inclusive.
To see this, observe that the condition that tells us that a substring s[i .. j] is the kind we're looking for is that
net[i .. j] = 0.
Adding net[1 .. i-1] to both sides of this equation gives
net[1 .. i-1] + net[i .. j] = net[1 .. i-1]
with the LHS then simplifying to just
net[1 .. j] = net[1 .. i-1]
Algorithm
That means that we can create a table containing two entries (first position seen and last position seen) for each possible distinct value that we could get as we calculate a running sum of net values. This running total could range as low as -n (if every character is a consonant) or as high as n (if every character is a vowel), so there are at most 2n+1 distinct such sums in total, so we'll need that many rows in our table. We then march through the string from left to right calculating a running total net value, and updating the pair in the table that corresponds to the current running total, noticing whenever this update produces a new, maximum-length substring. In pseudocode, with zero-based array indices and using separate arrays to hold the elements in each pair:
- Create 2 arrays of length 2n+1, first[] and last[], initially containing all -2s, except for first[n] which is -1. (Need to use -2 as a sentinel since -1 is actually a valid value!)
- Set bestFirst = bestLast = bestLen = -1.
- Set the running total t = n. (n "means" 0; using this bias just means we can use the running total directly as a nonnegative-valued index into the arrays without having to repeatedly add an offset to it.)
- For i from 0 to n-1:
- If s[i] is a vowel, increment t, otherwise decrement t.
- If first[t] is -2:
- Otherwise:
- Set last[t] = i.
- If last[t] - first[t] > bestLen:
- Set bestLen = last[t] - first[t].
- Set bestFirst = first[t] + 1.
- Set bestLast = last[t].
A maximum-length range will be returned in (bestFirst, bestLast), or if no such range exists, these variables will both be -1.
I remember seeing this solution, or one very similar to it, somewhere on SO a while back -- if anyone can find it, I'll gladly link to it.