Encode and Decode Strings
Table of Contents + β
This problem looks easy until you hit the trap. You have a list of strings. You must squash them into one single string. Then you must split that single string back into the exact same list. The trap is that the strings can contain any character. So you cannot just glue them with a comma and hope.
π― The Problem
You squash a list of strings into one string, then split it back into the exact same list.
- Turning the list into one string is called encoding. Encoding means packing data into a single form you can send or store.
- Turning that one string back into the list is called decoding.
- The strings can contain anything. Commas. Spaces. Even the
#mark. - So your method must work for any character inside the words.
For ["hello", "world"]: you encode it into one string, then decode that string back to ["hello", "world"], exactly.
Input: ["hello", "world"]Encoded: "5#hello5#world"Output: ["hello", "world"]
Explanation: Each word is stored as its length, a # mark, then the word.Here is the problem drawn out. We squash the list into one string, then pull it back apart.
π’ Approach 1: Join With a Separator (Brute Force)
The idea in one line: glue the words with a marker character, then split on it.
The idea:
- A separator is a marker you put between items so you know where one ends.
- Like a comma. So
["hello", "world"]becomes"hello,world". - To decode, split on the comma.
Why it is weak:
- A word can contain the separator itself.
- Say the list is
["a,b", "c"]. You glue it into"a,b,c". Splitting gives three pieces, not two. The data is broken. - A rarer separator does not help. Strings can hold any character, so no single separator is ever safe.
Here is the separator-based code for that idea:
class Codec: def encode(self, strs): return "#".join(strs)
def decode(self, s): return s.split("#")β‘ Approach 2: Length-Prefix Encoding (Best)
The idea in one line: write each wordβs length before the word, so the decoder reads by count, not by searching.
The idea:
- This is length-prefix encoding. It stores the size of each piece right before the piece.
- Before each word, write its length, then a
#, then the word. - So
"hello"becomes"5#hello"and"world"becomes"5#world". Glued:"5#hello5#world".
How decode works:
- Read digits until you hit the
#. Those digits are the length. - Read exactly that many characters after the
#. That is one word. - Jump to the next position and repeat until the string is used up.
Why it never breaks:
- The decoder never searches for a marker inside the word.
- It reads the count, then grabs exactly that many characters and stops.
- A
#or comma inside the word cannot fool it. The length gave the exact size.
Here is the decode dry run on "5#hello5#world".
Steps to Solve
- To encode, start with an empty result string.
- For each word, append its length, then a
#, then the word itself. - Return the joined result.
- To decode, start at position zero with an empty list.
- Read characters as digits until you reach a
#. Those digits form the length number. - Move one step past the
#, then read exactly that many characters as the next word. - Add the word to the list and move the position forward.
- Repeat until you reach the end of the string, then return the list.
This Python version joins each word with its length and a #, then reads them back one by one.
def encode(words): result = "" for w in words: result += str(len(w)) + "#" + w # length#word return result
def decode(s): result = [] i = 0 while i < len(s): j = i while s[j] != "#": # read the digits of the length j += 1 length = int(s[i:j]) # the number before the # word = s[j + 1:j + 1 + length] # read exactly that many chars result.append(word) i = j + 1 + length # jump to the next word return result
words = ["hello", "world"]encoded = encode(words)print(encoded)print(decode(encoded))The output of the above code will be:
5#hello5#world['hello', 'world']Let us read the Python version line by line. The decode part is the one interviewers probe.
In encode we start with an empty result. For each word we add three things: the length as text, a #, then the word. So "hello" becomes "5#hello". We return the joined string.
In decode we start with an empty result list and an index i at zero. The outer while runs until i reaches the end of the string.
Inside, we set j = i. Then the inner while s[j] != "#" moves j forward until it lands on the #. So now everything between i and j is the length written as digits.
The line length = int(s[i:j]) turns those digits into a real number. This is the size of the next word.
The line word = s[j + 1:j + 1 + length] is the heart of it. We skip past the # with j + 1. Then we read exactly length characters. Because we trust the length, we never guess where the word ends. We know its exact size. So a # inside the word cannot fool us.
We append the word. Then i = j + 1 + length jumps the index to the start of the next chunk. We loop again until the whole string is read.
This is why length-prefix encoding is safe for any character. The decoder reads by count, not by searching for a marker.
β±οΈ Time and Space Complexity
Both encode and decode walk through every character once. So both run in O(total characters) time. The encoded string holds all the words plus a small length tag for each, so the extra space is also linear in the input size. The naive separator way looks the same on speed but it is simply wrong, so it does not belong in the comparison of correct methods.
| Approach | Time Complexity | Space Complexity |
|---|---|---|
| Join with a separator (broken) | O(n) | O(n) |
| Length-prefix encoding | O(n) | O(n) |
Tip
The key insight to say out loud: store the length, not a separator. A separator can appear inside the data and break it. A length tells the decoder exactly how many characters to read, so any character is safe inside a word.
π§© Key Takeaways
- β A single separator like a comma fails because the data can contain that same character.
- β
Length-prefix encoding stores each word as its length, a
#, then the word. - β The decoder reads the length first, then grabs exactly that many characters.
- β
Because we read by count, any character inside a word is safe, even
#. - β Both encode and decode run in linear time over the total characters.
Check Your Knowledge
Test what you learned. Pick an answer for each question, then click Check.
- 1
Why does joining strings with a single comma separator fail?
Why: If a word contains the separator character, the decoder splits in the wrong place and corrupts the data.
- 2
In length-prefix encoding, what does the number before the # mean?
Why: The number is the length, so the decoder knows exactly how many characters the next word has.
- 3
Why is length-prefix encoding safe for any character inside a word?
Why: Because the decoder reads exactly the stated number of characters, a # or comma inside the word cannot confuse it.
- 4
What is the time complexity of decoding with length prefixes?
Why: Decoding walks through every character once, so it is linear in the total number of characters.