Goat Latin: String Transformation Word by Word
LeetCode 824, Goat Latin, asks you to transform a sentence one word at a time according to a small set of rules. Each word is modified based on whether it starts with a vowel or a consonant, and every word also picks up a trailing suffix that grows with its position in the sentence.
The rules are:
- If a word begins with a vowel (
a,e,i,o,u, case-insensitive), append"ma"to the end. - If a word begins with a consonant, move the first letter to the end of the word, then append
"ma". - For the k-th word (1-indexed), append
kcopies of"a"after the"ma".
Given the input "I speak Goat Latin", the expected output is "Imaa peaksmaaa oatGmaaaa atinLmaaaaa".
The approach
You can solve this in a single pass through the words:
- Split the sentence on spaces to get individual words.
- Iterate over each word with its index.
- Check whether the first character is a vowel or consonant.
- Transform the word accordingly: leave it as-is for vowels, or rotate the first character to the end for consonants.
- Append
"ma"followed by(index + 1)copies of"a". - Join all transformed words back together with spaces.
The logic is entirely local to each word. No word depends on any other word's transformation. That makes the code clean and the reasoning simple.
Python solution
def toGoatLatin(sentence):
vowels = set("aeiouAEIOU")
words = sentence.split()
result = []
for i, word in enumerate(words):
if word[0] in vowels:
transformed = word + "ma"
else:
transformed = word[1:] + word[0] + "ma"
transformed += "a" * (i + 1)
result.append(transformed)
return " ".join(result)
The vowel set includes both lowercase and uppercase letters so the check works regardless of case. The consonant branch slices off the first character with word[1:], appends the original first character at the end, then adds "ma". The "a" * (i + 1) expression generates the right number of trailing "a" characters since i is zero-indexed.
Step-by-step walkthrough
Here is the full transformation of "I speak Goat Latin", traced word by word.
Step 1: Split the sentence into words
Split on spaces to get individual words. We process each word independently based on its position (1st, 2nd, 3rd, ...).
Step 2: Check the first character of each word
Compare the first character (case-insensitive) against the set {a, e, i, o, u}. This determines which transformation rule to apply.
Step 3: Apply the transformation rule
Vowel words: append "ma" directly. Consonant words: move the first letter to the end, then append "ma".
Step 4: Append "a" characters based on word position
The 1st word gets one "a", the 2nd gets two, the 3rd gets three, and so on. The count matches the 1-indexed position of the word.
Step 5: Join the transformed words
Rejoin all transformed words with a single space to produce the final output.
Each word is handled independently. The only thing that changes from one word to the next is the number of trailing "a" characters.
Complexity
| Complexity | |
|---|---|
| Time | O(n) |
| Space | O(n) |
Every character in the sentence is visited once during the split, once during the transformation, and once during the join. The suffix characters add at most O(k) work per word where k is the word index, but across all words the total suffix length is O(n) in the worst case (where n is the total length of the output). Space is O(n) for storing the result list and the final joined string.
Building blocks
This problem exercises two fundamental string skills that appear across many LeetCode problems.
Character classification
Checking whether a character belongs to a specific set (vowels, digits, letters) is a building block you will use constantly. Here it determines the transformation branch. The same idea shows up in problems like Valid Palindrome (filtering non-alphanumeric characters) and Find the Index of the First Occurrence in a String (character-by-character comparison). Building a quick lookup with a set keeps the check at O(1).
String rotation and concatenation
Moving the first character to the end is a minimal rotation. The slice-and-concatenate pattern word[1:] + word[0] is a technique that generalizes to rotating strings by any number of positions. You will see similar slicing logic in Reverse Words in a String (rearranging tokens) and Reverse String (reordering characters in place). Understanding how slicing maps to character positions makes these operations feel natural.
Edge cases
Single word, vowel start. "apple" becomes "applema" with one trailing "a", giving "applemaa". No rotation needed.
Single word, consonant start. "goat" becomes "oatgma" with one trailing "a", giving "oatgmaa". The "g" moves to the end before "ma" is appended.
All vowels. A sentence like "I eat oats" applies the vowel rule to every word. No character rotation happens at all.
Mixed case. "Each" starts with "E", which is a vowel. Your vowel check must be case-insensitive. Using a set that includes both cases (or calling .lower() on the first character) handles this.
From understanding to recall
The rules for Goat Latin are simple enough to remember after reading them once. But reproducing the code from scratch is a different challenge. The details that trip people up are small: remembering to check both cases for vowels, using 1-indexed counts for the trailing "a" characters, slicing correctly with word[1:] + word[0] instead of something off by one.
These details are exactly the kind of thing that fades after a few days. Spaced repetition targets that gap. Instead of re-reading the solution when you forget a detail, you reconstruct the code actively, which is what locks it into long-term memory. Each review takes less time than the last, and after a few rounds the whole solution becomes automatic.
Related posts
- Reverse String - The simplest in-place character manipulation problem, building the same slicing intuition
- Reverse Words in a String - Another word-by-word transformation that uses split, process, and join
- Find the Index of the First Occurrence in a String - Character-by-character comparison skills that complement the classification logic used here
Goat Latin is a good warm-up for building fluency with string operations. The transformation rules are mechanical, but implementing them cleanly requires comfort with slicing, set lookups, and positional indexing. Once those pieces are solid, you can apply them to harder string problems without hesitation.