class Solution:
def removeDuplicateLetters(self, s: str) -> str:
#집합으로 정렬
for char in sorted(set(s)):
suffix = s[s.index(char):]
#전체 집합과 접미사 집합이 일치할 때 분리 진행
if set(s) == set(suffix):
return char + self.removeDuplicateLetters(suffix.replace(char,''))
return ''
사전식 순서
: 글자 그대로 사전에서 가장 먼저 찾을 수 있는 순서
bcabc에서 중복 문자를 제외하면 사전에서 가장 먼저 찾을 수 있는 문자열은 abc가 될 것이다.
만약, 앞에 e 문자가 하나 더 붙은 ebcabc가 입력값이라면 결과는 eabc가 될 것이다.
e 문자 자체는 해당 문자열 내에서 사전 순으로는 가장 뒤에 있지만, e는 입력값에서 딱 한 번만 등장하고 a, b, c는 뒤이어 등장하기 때문에 e의 위치를 변경할 수 없기 때문이다.
반면, 입력값이 ebcabce라면 첫 번째 e는 중복으로 제거할 수 있고, 마지막 e를 남겨서 결과는 abce가 될 수 있을 것이다.
중복 문자를 제외한(여기서는 집합으로 처리) 알파벳 순으로 문자열 입력값을 모두 정렬한 다음, 가장 빠른 a부터 접미사 suffix를 분리하여 확인한다.
다음 순서는 b인데, b의 경우 c,d가 뒤에 올 수 없기 때문에 이를 기준으로 분리할 수 없다.
분리 가능 여부는 이 코드와 같이 전체 집합과 접미사 집합이 일치하는지 여부로 판별한다.
이제 여기서 부터 실제로 분리를 처리하게 되는데, 현재 문자, 즉 c를 리턴하는 재귀호출 구조로 처리한다.
<재귀를 이용한 분리>
class Solution:
def removeDuplicateLetters(self, s: str) -> str:
counter, seen, stack = collections.Counter(s), set(), []
for char in s:
counter[char] -= 1
if char in seen:
continue
#뒤에 붙일 문자가 남아 있다면 스택에서 제거
while stack and char < stack[-1] and counter[stack[-1]] > 0:
seen.remove(stack.pop())
stack.append(char)
seen.add(char)
return ''.join(stack)
먼저 스택에는 앞에서부터 차례대로 쌓아 나간다
만약, 현재 문자 char가 스택에 쌓여 있는 문자 (이전 문자보다 앞선 문자)이고, 뒤에 다시 붙일 문자가 남아 있다면(카운터가 0 이상이라면), 쌓아둔 걸 꺼내서 없앤다.
카운팅에는 collections.Counter()를 이용한다.
seen은 집합 자료형(set)으로 이미 처리된 문자 여부를 확인하기 위해 사용했으며, 이처럼 이미 처리된 문자는 스킵한다.
처리된 문자 여부를 확인하기 위해 in을 이용한 검색 연산으로 찾아냈다.
해당 기능은 스택에는 존재하지 않는 연산이기 때문에 별도의 집합 자료형에만 사용했다.
리스트는 검색도 잘 지원하기 때문에 굳이 스택이라는 자료구조로 한정짓지 않고 풀이한다면 seen변수 없이도 다음과 같은 형태로 얼마든지 풀이가 가능하다.
def removeDuplicateLetters(self, s:str) -> str:
counter, stack = collections.Counter(s), []
for char in s:
counter[char] -= 1
if char in stack:
continue
# 뒤에 붙일 문자가 남아 있다면 스택에서 제거
while stack and char < stack[-1] and counter[stack[-1]] > 0:
stack.pop()
stack.append(char)
return ''.join(stack)
<스택 문자 제거 풀이>
cbacdcba에서 a가 들어올 때, 이미 이전에 들어와 있던 c와 b는 다음과 같이 제거된다.