Problem Overview
LeetCode 1704 Determine If String Halves Are Alike gives you a string s of even length. Split it down the middle into two halves of equal size. The two halves are called "alike" if the number of vowels in the first half equals the number of vowels in the second half.
Vowels are the characters a, e, i, o, u in lowercase and A, E, I, O, U in uppercase. Both halves must be checked against both cases because the problem guarantees the string may contain uppercase letters. The string length is always even, so no boundary edge case exists.
This is a clean counting problem: iterate once through the string, track which half each character belongs to, and accumulate vowel counts. A final equality check returns the answer. No sorting, no hash map, no recursion needed.
- s has even length — guaranteed by constraints
- Split at index len/2 — first half [0, len/2), second half [len/2, len)
- Vowels: a, e, i, o, u and their uppercase forms A, E, I, O, U
- Count vowels in each half independently
- Return true if count(first half) == count(second half)
Simple Count and Compare
The most direct approach uses two counters: leftVowels and rightVowels. Iterate through the first half of the string (indices 0 to n/2 - 1) and increment leftVowels for each vowel character. Iterate through the second half (indices n/2 to n - 1) and increment rightVowels for each vowel. Return leftVowels == rightVowels.
This runs in O(n) time because both halves together span the entire string of length n. Space is O(1) because the vowel set has exactly 10 fixed elements and the two counters are scalar integers. There is no dependency between the two passes, so the code is easy to reason about and verify.
In Python this fits in three lines: mid = len(s) // 2; vowels = set("aeiouAEIOU"); return sum(c in vowels for c in s[:mid]) == sum(c in vowels for c in s[mid:]). The built-in sum generator keeps the intent crystal clear.
Simplest Approach
This is as simple as it gets: one pass counting vowels in each half. No tricks, no data structures — just clean counting with a vowel set. The two-counter approach matches the problem statement word for word and has zero algorithmic overhead.
Single-Counter Optimization
Instead of maintaining two separate counters, use a single balance counter. Increment the balance when a vowel appears in the first half and decrement it when a vowel appears in the second half. After traversing the entire string, return balance == 0.
This single-counter approach consolidates the logic into one loop. At every index i, check whether i < n/2 (first half) or i >= n/2 (second half). If the character at i is a vowel, add +1 or -1 accordingly. If the counts are truly equal the positive increments will exactly cancel the negative decrements.
The asymptotic complexity remains O(n) time and O(1) space, but the single loop eliminates the slicing overhead of the two-pass version and makes the balance metaphor explicit. A balanced string — where every first-half vowel is matched by a second-half vowel — returns zero.
- 1Initialize balance = 0, vowels = set("aeiouAEIOU"), n = len(s), mid = n // 2
- 2For i in range(n):
- 3 If s[i] in vowels:
- 4 If i < mid: balance += 1 (first half vowel)
- 5 Else: balance -= 1 (second half vowel)
- 6Return balance == 0
Vowel Set for O(1) Membership Check
The vowel lookup is the innermost operation in the loop, so its cost matters. Using a Python set or a Java HashSet with the 10 characters "aeiouAEIOU" gives O(1) average-case membership check. A string comparison like "aeiouAEIOU".indexOf(c) >= 0 would be O(10) — technically O(1) but with a larger constant. The set is cleaner.
In Java, a common alternative is a boolean array of size 128 (ASCII range) initialized for the 10 vowel positions. Array lookups by character index are O(1) with near-zero overhead and no hashing cost. For a problem this straightforward, all approaches perform equivalently in practice.
The 10-element vowel set — five lowercase, five uppercase — covers all cases in one lookup. There is no need to call Character.toLowerCase() or s.lower() before checking. Storing both cases in the set avoids any transformation on each character.
Balance Approach
The single-counter approach is marginally cleaner: balance = sum(1 if c in vowels else 0 for c in s[:mid]) - sum(1 if c in vowels else 0 for c in s[mid:]); return balance == 0. The balance metaphor makes the equality check feel natural — you are literally checking that the string is balanced across its midpoint.
Walk-Through Example
Consider s = "book". Length is 4, mid = 2. First half = "bo", second half = "ok". Scanning "bo": b is not a vowel, o is a vowel — leftVowels = 1. Scanning "ok": o is a vowel — rightVowels = 1, k is not a vowel. 1 == 1, return true.
Now consider s = "textbook". Length is 8, mid = 4. First half = "text", second half = "book". Scanning "text": t not vowel, e is vowel, x not vowel, t not vowel — leftVowels = 1. Scanning "book": b not vowel, o is vowel, o is vowel, k not vowel — rightVowels = 2. 1 != 2, return false.
The contrast between these two examples shows that a string can look balanced (same total length per half) while being vowel-unbalanced. The problem cares only about vowel count parity, not character diversity, length, or any other property.
- 1s="book", n=4, mid=2, vowels={"a","e","i","o","u","A","E","I","O","U"}
- 2First half "bo": b→skip, o→leftVowels=1
- 3Second half "ok": o→rightVowels=1, k→skip
- 41 == 1 → return true
- 5s="textbook", n=8, mid=4
- 6First half "text": t→skip, e→leftVowels=1, x→skip, t→skip
- 7Second half "book": b→skip, o→rightVowels=1, o→rightVowels=2, k→skip
- 81 != 2 → return false
Code Walkthrough — Python and Java
Python solution using sum and set: vowels = set("aeiouAEIOU"); mid = len(s) // 2; return sum(c in vowels for c in s[:mid]) == sum(c in vowels for c in s[mid:]). This is idiomatic Python — the generator expressions are lazy, the set lookup is O(1), and the whole thing reads like the problem statement.
Java solution using a char set and a for loop: define Set<Character> vowels = Set.of('a','e','i','o','u','A','E','I','O','U'); int left = 0, right = 0, mid = s.length() / 2; for (int i = 0; i < mid; i++) if (vowels.contains(s.charAt(i))) left++; for (int i = mid; i < s.length(); i++) if (vowels.contains(s.charAt(i))) right++; return left == right. Both achieve O(n) time and O(1) space.
An alternative Java idiom uses a single loop with the ternary: for each index check i < mid to decide which counter to increment. This mirrors the balance approach and keeps the loop body compact. The choice between one-loop and two-loop styles is purely stylistic for this problem.
Include Both Cases
Include BOTH uppercase and lowercase vowels in your set: "AeIoU" style inputs are valid per constraints. The problem statement uses lowercase examples but the constraints explicitly permit uppercase letters. Forgetting uppercase vowels causes wrong answers on test cases like "AaAa" or "AbBa".