Logo

Encode and Decode Strings

LeetCode의 271번째 문제인 Encode and Decode Strings를 함께 풀어보도록 하겠습니다.

이 문제는 LeetCode에서 유료 구독자만 접근할 수 있습니다. LintCode의 659번째 문제가 거의 동일하며 무료로 푸실 수 있으니 참고 바랍니다.

문제

문자열 목록을 문자열로 인코딩하는 알고리즘을 설계하십시오. 인코딩된 문자열은 네트워크를 통해 전송되고 다시 원래의 문자열 목록으로 디코딩됩니다.

encodedecode 메서드를 구현하시오. eval과 같은 직렬화 메서드를 사용해서 문제를 푸시면 안 됩니다.

strs[i]에는 256개의 유효한 ASCII 문자 중 어떤 문자든 포함될 수 있습니다.”

예제

입력: ["Hello","World"]
출력: ["Hello","World"]
입력: [""]
출력: [""]

풀이 1

Medium 난이도 임에도 불구하고 이 문제는 얼핏 보면 아주 쉬워보일 수 있는데요. 문자열 목록을 문자열로 인코딩할 때 문자 사이에 구분자를 넣으면 될 것 같기 때문입니다.

그런데 여기서 주의할 점은 문자열에 256개의 유효한 ASCII 문자 중 어떤 문자든지 포함될 수 있다는 것인데요. 따라서 반드시 구분 기호(delimiter)로 ASCII 문자가 아닌 다른 문자를 사용해야겠습니다.

우리 채팅할 때 자주 사용하는 😄 이모지로 문자열을 구분해보면 어떨까요? 첫 번째 예제에서 주어진 배열을 상대로 적용해보면 다음과 같은 형태로 인코딩과 디코딩을 거쳐 원래 문자열을 얻을 수 있을 것입니다.

["Hello", "World"] -- 인코딩 --> "Hello😄World" -- 디코딩 --> ["Hello", "World"]

그럼 이 간단한 알고리즘을 코드로 구현해보겠습니다.

class Codec:
    def encode(self, strs: List[str]) -> str:
        return "😄".join(strs)

    def decode(self, s: str) -> List[str]:
        return s.split("😄")

입력 배열이 담고 있는 문자열의 개수를 n라고 했을 때, 이 풀이의 시간 복잡도는 O(n)입니다. join()split() 함수의 수행 시간은 문자열 개수와 비례하기 때문입니다.

공간 복잡도는 결과 값이 차지하는 메모리를 제외하면 추가적인 메모리를 사용하는 부분이 없으므로 O(1)이 되겠습니다.

풀이 2

이전 풀이는 좀 반칙같은 느낌이 들죠? 이번에는 좀 더 코딩 테스트에 걸맞는 알고리즘을 생각해보겠습니다.

만약에 구분 기호로 ASCII 문자 중 하나를 선택해야 한다면 이 문제를 해결할 수 있을까요? 그럼 문자열 안에도 구분자가 들어있을 가능성이 있기 때문에 좀 더 까다로워지겠죠?

예를 들어, 입력으로 다음과 같은 문자열 배열이 :로 문자열을 구분했다고 가정해봅시다.

["Hello", "World", "Yes:Or:No"]

위 알고리즘으로 인코드를 한다면 다음과 같은 문자열이 나와서 뭐가 구분자이고 뭐가 문자열의 일부인지 알 수가 없겠죠?

"Hello:World:Yes:Or:No"

만약에 :를 구분자로 디코드를 하면 다음과 같이 5개의 문자열이 나올 것입니다. 입력 배열과 동일하지 않죠.

["Hello", "World", "Yes", "Or", "No"]

어떻게 하면 각 문자열 내부에서 구분자와 동일한 기호에서 문자열이 쪼개지는 것을 피할 수 있을까요?

한 가지 방법은 구분자 이후에 나오는 문자열의 길이도 인코딩 결과에 기록을 해두는 것입니다. 예를 들어, 다음과 같이 문자열 길이를 첨가하여 디코딩을 할 수 있을 것입니다.

"5:Hello5:World9:Yes:Or:No"

그러면 원치 않게 문자열의 중간에서 구분이 될 위험이 사라지게 되겠죠?

   --5--  --5--  ----9----
"5:Hello5:World9:Yes:Or:No"

이 알고리즘을 파이썬으로 구현해보겠습니다.

class Codec:
    def encode(self, strs: List[str]) -> str:
        text = ""
        for str in strs:
            text += f"{len(str)}:{str}"
        return text

    def decode(self, s: str) -> List[str]:
        ls, start = [], 0
        while start < len(s):
            mid = s.find(":", start)
            length = int(s[start:mid])
            ls.append(s[mid + 1 : mid + 1 + length])
            start = mid + 1 + length
        return ls

이 풀이의 시간 복잡도와 공간 복잡도는 이전 풀이와 동일하게 O(n)O(1)이 되겠습니다.

위 코드에서 사용된 파이썬의 f-string에 대해서는 관련 포스팅을 참고하세요.

마치면서

이 문제는 Blind 75에 속하는 매우 유명한 문제이지만 저는 솔직히 개인적으로 그닥 선호하는 문제는 아니에요. 면접관으로서 지원자의 어떤 능력을 평가하려는 건지 출제 의도가 분명하지 않다고 생각하는데 여러분은 어떠신가요?