Problem overview
The class Codec has two methods. encode(strs) turns an array of strings into one string, and decode(s) turns that string back into the same array, in the same order. The two methods run on different Codec objects, so decode can rely only on the string it receives, not on anything encode remembered.
The strings may hold any printable character, including digits, spaces and #, and they may be empty. For ["lint", "code", "love", "you"] the encoded string is "4#lint4#code4#love3#you", and decoding it gives the same four strings. For ["3#a", "", "#"] it is "3#3#a0#1##". The empty array encodes to the empty string, and [""] encodes to "0#", so the two can't be confused.
Approach: write the length first
The obvious idea is to join the strings with a separator such as #. It fails because any character you pick can also appear inside a string: ["a#b"] and ["a", "b"] would both become "a#b".
A length prefix avoids the problem. Before each string, write how many characters it has, then a #, then the string. When decoding, you read the digits up to the next #, turn them into a number, and take exactly that many characters. Those characters are never inspected, so a # or a digit inside them can't be mistaken for a marker. The # after the length is still needed: without it, "3abc" followed by "12" would be ambiguous about where the length ends and the text begins.
Both methods can run in O(N), where N is the total length of all strings. Two things would break that: copying the whole encoded string again for every piece, and cutting s into shorter and shorter copies while decoding. The solution builds the result once and moves an index through s instead.
Step by step
encode
- Start
encodedas an empty string, declared withletbecause it grows. - Loop over the strings with
for...of, taking each one ass. - Append the length, a
#and the string itself. Adding a number to a string turns the number into its digits, sos.length + "#" + sworks without a conversion call. - Return
encodedafter the loop.
decode
- Create the result array
strsand an indexithat marks where the next piece starts. - Loop while
i < s.length. An emptysnever enters the loop, which gives the empty array. - Start
jatiand move it forward untils[j]is"#". Because a length contains only digits, the first#found fromiis the oneencodewrote after it. - Read the digits with
s.substring(i, j)and convert them withNumber. - Push the next
lengthcharacters, which start atj + 1, ontostrs. - Set
itoj + 1 + length, the start of the next piece. - Return
strs.
The solution in JavaScript
class Codec {
encode(strs) {
let encoded = "";
for (const s of strs) {
encoded += s.length + "#" + s;
}
return encoded;
}
decode(s) {
const strs = [];
let i = 0;
while (i < s.length) {
let j = i;
while (s[j] !== "#") {
j++;
}
const length = Number(s.substring(i, j));
strs.push(s.substring(j + 1, j + 1 + length));
i = j + 1 + length;
}
return strs;
}
}
The JavaScript details worth knowing:
+with a string. Ins.length + "#" + s, the first+has a number on the left and a string on the right, so JavaScript converts the number to text and joins them. Startingencodedas""keeps every+=a string operation.substring(start, end)stops beforeend. It takes the characters fromstartup to but not includingend, sosubstring(j + 1, j + 1 + length)returns exactlylengthcharacters. Ifendgoes past the string, it is clamped to the string's length rather than raising an error.Number("12")is12. UnlikeparseInt,Numberturns the whole text into a number or givesNaN. Here the text is always digits made byencode.!==compares without converting.s[j] !== "#"compares two strings directly.
Complexity
Time: O(N). encode appends one piece per string, and each character is written once, plus a few characters for each length and #. decode moves j over each length's digits and copies each string once, and i only moves forward. The digits of the lengths add at most a few characters per string, and the constraints keep each string under 200 characters, so a length has at most three digits.
Space: O(N) for the encoded string and for the decoded array, apart from a few variables.
Edge cases and common mistakes
- Empty list and list with an empty string.
[]gives""and[""]gives"0#". Decoding"0#"pushessubstring(2, 2), the empty string, and movesito 2, which ends the loop. - Strings that look like an encoding.
["12#ab"]encodes to"5#12#ab". The decoder reads 5, takes five characters, and never treats the inner12#as a length. - Splitting on
#. Callings.split("#")indecodebreaks as soon as a string holds a#. - Forgetting the separator after the length. Without the
#,"12"after a string of length 3 would read as312. - Slicing
srepeatedly. Writings = s.substring(...)to drop each piece copies the rest of the string every time, which makesdecodequadratic on long inputs.
Testing
["a", "b"]encodes to"1#a1#b"and decodes back to["a", "b"].["", "", ""]encodes to"0#0#0#"and decodes to three empty strings.["3#a", "", "#"]encodes to"3#3#a0#1##"and decodes to the same array.["12#ab"]encodes to"5#12#ab"and decodes to["12#ab"].
