Start learning free

LESSON 318 OF 536 · HASH TABLE

Encode and Decode Strings in Java: Length-Prefix Codec in O(N)

Practice O(n) Read the full explanation

Design a way to turn a list of strings into one string and back. One program encodes the list and sends the string to another program, which decodes it into the same list of strings, in the same order.

Implement the class Codec:

  • encode(strs) gets a list of strings strs and returns one string that holds all of them.
  • decode(s) gets a string s made by encode and returns the original list of strings.

The strings can contain any printable character, including #, digits and spaces, and a string can be empty. So no single character can mark where one string ends and the next begins: that character could be inside a string too.

Each test is one list. It is encoded with one Codec object and decoded with another, as if the string were sent to another machine, so decode can use only the string it gets. The output is the decoded list. The starter code spells the method names the way your language does.

 

Example 1:

Input: strs = ["lint","code","love","you"]
Output: ["lint","code","love","you"]
Explanation: encode can return "4#lint4#code4#love3#you": for each string, its length,
a #, then the string itself. decode reads the 4 before the first #, takes the 4 characters
after it, "lint", and goes on from there.

Example 2:

Input: strs = ["3#a","","#"]
Output: ["3#a","","#"]
Explanation: The encoded string is "3#3#a0#1##". decode reads the length 3 up to the first #,
then takes exactly 3 characters, "3#a", although they hold a digit and a #.
Then "0#" gives the empty string, and "1##" gives "#".

Example 3:

Input: strs = []
Output: []
Explanation: An empty list encodes to the empty string "", which decodes to no strings at all.
A list with one empty string, [""], encodes to "0#" instead, so the two stay different.

 

Constraints:

  • 0 <= strs.length <= 105
  • 0 <= strs[i].length <= 200
  • The total length of all the strings is at most 106.
  • strs[i] consists of printable ASCII characters (codes 32 to 126): letters, digits, spaces and punctuation such as #.

Note:

Both encode and decode should run in O(N) time, where N is the total length of all the strings.

Write each string's length in front of it, followed by a #: <length>#<string>. To decode, read the digits up to the next #, then take exactly that many characters, whatever they are; the next string starts right after them.

Keep both methods linear: don't copy the whole encoded string again for every string you add, and don't cut s into shorter and shorter copies while decoding; move an index through it instead.

STEP 1 OF 14
    Ctrl + Enter to check

    JAVA · HASH TABLE

    Encode And Decode Strings in Java, explained

    Problem overview

    The class Codec has two methods. encode takes a List<String> and returns one String that holds all of them. decode takes that string and returns the original list, in the same order. The lesson's tests encode with one Codec object and decode with another, so decode can rely only on the string it receives, never on fields set during encode.

    The strings may contain any printable character, including #, digits and spaces, and a string 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 back. The list ["3#a", "", "#"] encodes to 3#3#a0#1## and also decodes back unchanged.

    Approach: write the length first

    The obvious plan is to join the strings with a separator, such as #, and split on it later. That fails because any character you choose can also appear inside a string. If "#" is one of the strings, a split cuts it in the wrong place.

    The fix is to stop looking for a boundary at all. Before each string, write its length, then a #, then the string itself. The decoder reads the digits up to the first #, converts them to a number, and takes exactly that many characters, whatever they are. The next piece starts right after them. The characters inside a string are never inspected, so #, digits and spaces inside it cause no trouble.

    The # after the length is still needed. Without it, a string that starts with a digit would run into its own length: "1" followed by "2" could be read as the length 12. Because the length is made only of digits, the first # after the start of a piece is always the one encode wrote.

    Both methods must run in O(N), where N is the total length of all the strings. That shapes two details. The encoder uses a StringBuilder, since adding to a String in a loop copies everything built so far each time. The decoder moves an index through s and never cuts s into shorter copies.

    Step by step

    encode

    1. Create a StringBuilder named encoded to collect the pieces.
    2. Loop over each String s in strs.
    3. Append the piece for s: its length, the character '#', then s. append returns the builder, so the three calls chain on one line.
    4. Return encoded.toString() after the loop.

    decode

    1. Create the result list, an empty ArrayList named strs.
    2. Keep an index i at 0. It marks where the next piece starts.
    3. Loop while i < s.length(). Each round reads one piece.
    4. Start a second index j at i and move it forward until s.charAt(j) is '#'. The digits of the length lie between i and j.
    5. Parse the digits with Integer.parseInt(s.substring(i, j)) into length.
    6. Add the string itself: the length characters that start at j + 1. The end position of substring is exclusive, so it is j + 1 + length.
    7. Move i to j + 1 + length, where the next length begins.
    8. Return strs after the loop.

    The solution in Java

    class Codec {
        public String encode(List<String> strs) {
            StringBuilder encoded = new StringBuilder();
            for (String s : strs) {
                encoded.append(s.length()).append('#').append(s);
            }
            return encoded.toString();
        }
    
        public List<String> decode(String s) {
            List<String> strs = new ArrayList<>();
            int i = 0;
            while (i < s.length()) {
                int j = i;
                while (s.charAt(j) != '#') {
                    j++;
                }
                int length = Integer.parseInt(s.substring(i, j));
                strs.add(s.substring(j + 1, j + 1 + length));
                i = j + 1 + length;
            }
            return strs;
        }
    }
    

    A few Java details matter here:

    • append overloads. StringBuilder.append accepts an int, a char and a String. append(s.length()) adds the decimal digits of the number, not a character with that code. append('#') adds one character.
    • The char comparison. s.charAt(j) != '#' compares two char values with !=, which is correct for primitives. Strings, by contrast, must be compared with equals.
    • substring(start, end) includes start and excludes end. Since Java 7 it copies only the requested characters, so each string costs time proportional to its own length.
    • ArrayList<> uses the diamond operator, so the element type comes from the declared type List<String>.

    Complexity

    Time: O(N) for both methods. encode appends each string once, and the length digits add a small amount per string. decode visits each character of s once: the inner loop scans the digits, and substring copies the piece. The index i only moves forward.

    Space: O(N). The encoded string and the decoded list each hold all the characters.

    Edge cases and common mistakes

    • Empty list. It encodes to the empty string, and decode("") never enters the loop, so it returns an empty list.
    • A list with one empty string. It encodes to 0#, which differs from the empty string. Decoding 0# gives a list of size 1, and decoding "" gives a list of size 0, so the two cases stay apart.
    • Strings that look like the format. ["12#", "a b"] encodes to 3#12#3#a b. The decoder reads 3, takes 12#, reads the next 3, and takes a b. It never reads inside a string.
    • Using a separator only. Splitting on # breaks for any string that contains one.
    • Skipping the # after the length. A string starting with a digit can no longer be told apart from its length.
    • Forgetting + 1. Starting the substring at j includes the # in the string, and moving i to j + length lands one character too early.
    • Using String += in encode, or cutting s into shorter copies in decode. Both give correct results but copy the data repeatedly, which is too slow for large inputs.
    • Counting length() as bytes. length() counts char values, and substring uses the same unit, so the two stay consistent for any text.

    Testing

    • ["lint", "code", "love", "you"] encodes to 4#lint4#code4#love3#you and decodes back to the same list.
    • ["3#a", "", "#"] encodes to 3#3#a0#1## and decodes back to the same list.
    • [] encodes to the empty string and decodes to an empty list.
    • ["", ""] encodes to 0#0# and decodes to two empty strings.
    • ["12#", "a b"] encodes to 3#12#3#a b and decodes back to the same list.
    Keep going with Java500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free