LeetCode 3136 - Valid Word
The problem asks us to determine whether a given string qualifies as a "valid word" according to four specific rules. First, the word must contain at least 3 characters. Any string shorter than 3 is automatically invalid.
Difficulty: 🟢 Easy
Topics: String
Solution
Problem Understanding
The problem asks us to determine whether a given string qualifies as a "valid word" according to four specific rules.
First, the word must contain at least 3 characters. Any string shorter than 3 is automatically invalid.
Second, every character in the string must be either:
- A lowercase English letter
- An uppercase English letter
- A digit from
0to9
If the string contains any other symbol such as @, #, or $, the word is invalid immediately.
Third, the word must contain at least one vowel. The valid vowels are:
- Lowercase:
a,e,i,o,u - Uppercase:
A,E,I,O,U
Fourth, the word must contain at least one consonant. A consonant is any English alphabet letter that is not a vowel.
The input is a single string word, and the output is a boolean:
- Return
trueif all conditions are satisfied - Return
falseotherwise
The constraints are very small:
1 <= word.length <= 20
This tells us that performance is not a concern. Even a straightforward scan of the string is more than fast enough.
Several edge cases are important:
- Strings shorter than 3 characters
- Strings containing invalid symbols such as
$ - Strings containing only digits
- Strings containing vowels but no consonants
- Strings containing consonants but no vowels
- Mixed uppercase and lowercase vowels
A naive implementation can easily make mistakes if it:
- Treats digits as consonants
- Forgets uppercase vowels
- Fails to reject invalid symbols immediately
Approaches
Brute Force Approach
A brute force approach would repeatedly scan the string multiple times.
One pass could check whether all characters are valid. Another pass could search for vowels. A third pass could search for consonants. We could also separately check the length requirement.
This approach is correct because every rule is verified independently. However, it performs unnecessary repeated work by traversing the same string multiple times.
Even though the constraints are tiny and this would still pass easily, it is not the cleanest or most efficient solution.
Optimal Approach
The key observation is that all conditions can be checked in a single traversal of the string.
While iterating through each character:
- Reject invalid characters immediately
- Track whether we have seen at least one vowel
- Track whether we have seen at least one consonant
Digits are allowed, but they do not contribute to either the vowel or consonant requirement.
At the end of the scan:
- The word is valid only if both flags are
true
This approach is optimal because it minimizes both time and space usage while keeping the implementation simple and readable.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n) | O(1) | Multiple passes over the string |
| Optimal | O(n) | O(1) | Single pass with boolean tracking |
Algorithm Walkthrough
- First, check whether the length of the string is less than 3. If it is, return
falseimmediately because the minimum length requirement is violated. - Create a set containing all vowels:
a, e, i, o, u, A, E, I, O, U
Using a set allows constant time membership checks. 3. Initialize two boolean variables:
has_vowel = Falsehas_consonant = False
These variables track whether the required character types have appeared. 4. Iterate through every character in the string. 5. For each character:
-
If it is neither a letter nor a digit, return
falseimmediately because the string contains an invalid symbol. -
If it is a letter:
-
If it exists in the vowel set, set
has_vowel = True -
Otherwise, it must be a consonant, so set
has_consonant = True
- Digits are allowed, so no additional action is needed when encountering them.
- After processing all characters, return:
has_vowel AND has_consonant
Why it works
The algorithm maintains two important properties while scanning the string:
has_vowelis true if at least one vowel has appearedhas_consonantis true if at least one consonant has appeared
At the same time, invalid characters are rejected immediately. Since every character is examined exactly once, and every rule is verified during that traversal, the final result is correct.
Python Solution
class Solution:
def isValid(self, word: str) -> bool:
if len(word) < 3:
return False
vowels = set("aeiouAEIOU")
has_vowel = False
has_consonant = False
for char in word:
if not char.isalnum():
return False
if char.isalpha():
if char in vowels:
has_vowel = True
else:
has_consonant = True
return has_vowel and has_consonant
The implementation begins by handling the shortest invalid strings immediately. This prevents unnecessary processing.
The vowels set stores all uppercase and lowercase vowels so membership checks are efficient and simple.
Two boolean flags track whether the required letter categories have been encountered.
The loop processes each character exactly once:
char.isalnum()verifies that the character is either a letter or digitchar.isalpha()distinguishes letters from digits- Vowels and consonants are classified appropriately
Finally, the method returns True only when both required conditions are satisfied.
Go Solution
func isValid(word string) bool {
if len(word) < 3 {
return false
}
vowels := map[rune]bool{
'a': true, 'e': true, 'i': true, 'o': true, 'u': true,
'A': true, 'E': true, 'I': true, 'O': true, 'U': true,
}
hasVowel := false
hasConsonant := false
for _, char := range word {
isLetter := (char >= 'a' && char <= 'z') ||
(char >= 'A' && char <= 'Z')
isDigit := (char >= '0' && char <= '9')
if !isLetter && !isDigit {
return false
}
if isLetter {
if vowels[char] {
hasVowel = true
} else {
hasConsonant = true
}
}
}
return hasVowel && hasConsonant
}
The Go version follows the same algorithmic structure as the Python solution.
One important difference is that Go does not provide direct methods like isalnum() or isalpha() for rune values in the same way Python does for strings. Instead, the implementation manually checks ASCII ranges to determine whether a character is a letter or digit.
The vowel lookup uses a map[rune]bool for efficient membership testing.
Since the input size is tiny, there are no concerns about performance or memory overhead.
Worked Examples
Example 1
Input:
word = "234Adas"
Initial state:
| Variable | Value |
|---|---|
| has_vowel | False |
| has_consonant | False |
Processing steps:
| Character | Type | Action | has_vowel | has_consonant |
|---|---|---|---|---|
2 |
Digit | Allowed | False | False |
3 |
Digit | Allowed | False | False |
4 |
Digit | Allowed | False | False |
A |
Vowel | Set vowel flag | True | False |
d |
Consonant | Set consonant flag | True | True |
a |
Vowel | Already true | True | True |
s |
Consonant | Already true | True | True |
Final result:
True
Example 2
Input:
word = "b3"
Length check:
| Condition | Result |
|---|---|
| len(word) < 3 | True |
The algorithm immediately returns:
False
Example 3
Input:
word = "a3$e"
Initial state:
| Variable | Value |
|---|---|
| has_vowel | False |
| has_consonant | False |
Processing steps:
| Character | Type | Action |
|---|---|---|
a |
Vowel | Set vowel flag |
3 |
Digit | Allowed |
$ |
Invalid symbol | Return false immediately |
Final result:
False
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | Each character is processed once |
| Space | O(1) | Only a few boolean variables and a small vowel set are used |
The algorithm scans the string exactly one time. Since each operation inside the loop is constant time, the total runtime grows linearly with the length of the string.
The extra memory usage remains constant because the vowel set size never changes and only two boolean flags are stored.
Test Cases
solution = Solution()
assert solution.isValid("234Adas") == True # valid mixed string
assert solution.isValid("b3") == False # too short
assert solution.isValid("a3$e") == False # invalid symbol
assert solution.isValid("abc") == True # simple valid lowercase
assert solution.isValid("AE1") == False # vowels only, no consonant
assert solution.isValid("bcdf") == False # consonants only, no vowel
assert solution.isValid("123") == False # digits only
assert solution.isValid("a1b") == True # minimal valid case
assert solution.isValid("A1b") == True # uppercase vowel
assert solution.isValid("u9Z") == True # uppercase consonant
assert solution.isValid("@ab") == False # invalid leading symbol
assert solution.isValid("ab#") == False # invalid trailing symbol
assert solution.isValid("a") == False # single character
assert solution.isValid("12a") == False # vowel but no consonant
assert solution.isValid("12b") == False # consonant but no vowel
| Test | Why |
|---|---|
"234Adas" |
Standard valid example |
"b3" |
Length smaller than 3 |
"a3$e" |
Contains invalid symbol |
"abc" |
Simple valid lowercase word |
"AE1" |
Only vowels present |
"bcdf" |
Only consonants present |
"123" |
Digits alone are insufficient |
"a1b" |
Smallest valid mixed case |
"A1b" |
Uppercase vowel handling |
"u9Z" |
Uppercase consonant handling |
"@ab" |
Invalid symbol at start |
"ab#" |
Invalid symbol at end |
"a" |
Single-character edge case |
"12a" |
Missing consonant |
"12b" |
Missing vowel |
Edge Cases
One important edge case is very short strings such as "a" or "b3". A careless implementation might continue processing characters even though the word can never become valid. The solution handles this immediately with an early length check.
Another important case is strings containing invalid characters like "a3$e" or "ab#". Since the problem allows only letters and digits, these inputs must return false. The implementation checks every character with isalnum() in Python or explicit ASCII checks in Go, guaranteeing invalid symbols are rejected immediately.
A third important edge case is strings containing only digits, such as "123". Digits are allowed characters, but they do not count as vowels or consonants. Some incorrect solutions accidentally classify non-vowel characters as consonants. This implementation avoids that mistake by only classifying alphabetic characters as vowels or consonants.
Another subtle case is uppercase vowels like "A1b". If the vowel set only contains lowercase letters, uppercase vowels would be misclassified as consonants. The implementation explicitly includes both uppercase and lowercase vowels, ensuring correct behavior for mixed-case input.