LeetCode의 271번째 문제인 Encode and Decode Strings를 함께 풀어보도록 하겠습니다.
이 문제는 LeetCode에서 유료 구독자만 접근할 수 있습니다. LintCode의 659번째 문제가 거의 동일하며 무료로 푸실 수 있으니 참고 바랍니다.
문제
문자열 목록을 문자열로 인코딩하는 알고리즘을 설계하십시오. 인코딩된 문자열은 네트워크를 통해 전송되고 다시 원래의 문자열 목록으로 디코딩됩니다.
encode
와 decode
메서드를 구현하시오.
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에 속하는 매우 유명한 문제이지만 저는 솔직히 개인적으로 그닥 선호하는 문제는 아니에요. 면접관으로서 지원자의 어떤 능력을 평가하려는 건지 출제 의도가 분명하지 않다고 생각하는데 여러분은 어떠신가요?