pattern
: I, D로 구성된 문자열 ()
num
: pattern
에 따라 만들어진 정수로 구성된 문자열
길이 n
의 pattern
을 입력받아 만들 수 있는 길이 n+1
인 문자열 num
중 가장 작은 숫자를 구하는 문제이다.
아래의 조건을 만족하는 사전식 순서에서 가장 작은 문자열 num
을 반환한다.
0-indexed string이 만들어지는 조건
num
은 정수 1 ~ 9까지의 숫자로 구성되고, 각 숫자는 한번만 사용 가능I
→D
→
pattern
을 만족하는 모든 경우의 수를 구하고 그 중에 가장 작은 숫자를 찾으면 될 것이라 생각했다.
하지만 pattern의 제약 사항에 따라 시간 초과가 발생할 수 있을 것 같다.
만들면서 현재 가장 작다고 판단한 숫자를 기준으로 커지면 더 찾기 전에 종료하는 방법을 찾아야 할 것이다.
→ 이 때 Backtracking 개념을 떠올렸다.
📖 Backtracking (백트래킹)
- 해를 찾는 도중 해가 아니어서 막히면 되돌아가서 다시 해를 찾아가는 기법 → 최적화, 결정 문제 해결 방법
- 모든 가능한 경우의 수 중 특정 조건 만족하는 경우만 확인하는 것
- 모든 경우의 수 탐색(DFS)하면서 조건문 등으로 답이 될 수 없는 상황 정의해 탐색 중지 & 되돌아가기
- 일종의 가지치기를 통해 반복 횟수 감소 가능
- 가지치기를 얼마나 잘하느냐에 따라 효율성 결정
- DFS와 BFS
- DFS
- 모든 경우의 수(모든 경로) 탐색해 경우의 수 줄이지 못함
- 사용 👍 : 모든 경우의 수 고려해야 할 경우 BFS보다 좋음 (큐의 크기 엄청 커지기 때문)
- 사용 ❌ : 트리 깊이가 무한대가 될 경우
- BFS
- 모든 분기 검색하면서 상태공간 탐색 → 어느 한 부분에서 원하는 해 발견하면 최단 거리가 됨
따라서 이 문제는 DFS로 만들 수 있는 모든 num을 만들면서도, 조건에 맞지 않으면 만드는 것을 멈추는 식으로 구현하면 될 것이다.
탐색 깊이 →
최종 시간복잡도
최악의 경우 키의 수는 $O(9!)이 되는데 이는 충분히 수행 가능하다.
self.
으로 붙여서 클래스 멤버로 선언class Solution:
def smallestNumber(self, pattern: str) -> str:
# 내부에서 사용할 변수들은 self.으로 붙여서 클래스 멤버로 선언
self.visited = [0] * 10
self.nums = []
self.pattern = pattern
# DFS 호출
self.dfs([])
# 첫 번째 결과를 문자열로 변환해서 리턴
return ''.join(map(str, self.nums[0]))
def dfs(self, used_digits):
if len(used_digits) == len(self.pattern) + 1:
self.nums.append(used_digits)
return True
for digit in range(1, 10):
if self.visited[digit]:
continue
if not used_digits or (
self.pattern[len(used_digits) - 1] == 'I' and used_digits[-1] < digit or
self.pattern[len(used_digits) - 1] == 'D' and used_digits[-1] > digit
):
self.visited[digit] = 1
if self.dfs(used_digits + [digit]):
return True
self.visited[digit] = 0
return False
--