두 종류의 부등호 기호 ‘<’와 ‘>’가 k개 나열된 순서열 A가 있다. 우리는 이 부등호 기호 앞뒤에 서로 다른 한 자릿수 숫자를 넣어서 모든 부등호 관계를 만족시키려고 한다. 예를 들어, 제시된 부등호 순서열 A가 다음과 같다고 하자.
A ⇒ < < < > < < > < >
부등호 기호 앞뒤에 넣을 수 있는 숫자는 0부터 9까지의 정수이며 선택된 숫자는 모두 달라야 한다. 아래는 부등호 순서열 A를 만족시키는 한 예이다.
3 < 4 < 5 < 6 > 1 < 2 < 8 > 7 < 9 > 0
이 상황에서 부등호 기호를 제거한 뒤, 숫자를 모두 붙이면 하나의 수를 만들 수 있는데 이 수를 주어진 부등호 관계를 만족시키는 정수라고 한다. 그런데 주어진 부등호 관계를 만족하는 정수는 하나 이상 존재한다. 예를 들어 3456128790 뿐만 아니라 5689023174도 아래와 같이 부등호 관계 A를 만족시킨다.
5 < 6 < 8 < 9 > 0 < 2 < 3 > 1 < 7 > 4
여러분은 제시된 k개의 부등호 순서를 만족하는 (k+1)자리의 정수 중에서 최댓값과 최솟값을 찾아야 한다. 앞서 설명한 대로 각 부등호의 앞뒤에 들어가는 숫자는 { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }중에서 선택해야 하며 선택된 숫자는 모두 달라야 한다.
첫 줄에 부등호 문자의 개수를 나타내는 정수 k가 주어진다. 그 다음 줄에는 k개의 부등호 기호가 하나의 공백을 두고 한 줄에 모두 제시된다. k의 범위는 2 ≤ k ≤ 9 이다.
2
< >
9
> < < < > > > < <
여러분은 제시된 부등호 관계를 만족하는 k+1 자리의 최대, 최소 정수를 첫째 줄과 둘째 줄에 각각 출력해야 한다. 단 아래 예(1)과 같이 첫 자리가 0인 경우도 정수에 포함되어야 한다. 모든 입력에 답은 항상 존재하며 출력 정수는 하나의 문자열이 되도록 해야 한다.
897
021
9567843012
1023765489
❎ 1차 시도
import itertools
k = int(input())
signs = " " + input().strip() + " "
format_signs = signs.replace(" ", "{}") # 추후 포매팅하기 위해 형식 변경
# 가능한 모든 경우
cases = list(itertools.permutations(range(10), k+1))
# 최댓값 탐색
for case in cases[::-1]:
if eval(format_signs.format(*case)): # 부등호에 잘 맞아떨어질 경우
print(''.join(map(str, case)))
break
# 최솟값 탐색
for case in cases:
if eval(format_signs.format(*case)): # 부등호에 잘 맞아떨어질 경우
print(''.join(map(str, case)))
break
이 문제는 다양한 숫자 조합을 부등호 사이에 넣어 적합한지 확인하는 완전탐색 문제이다. 0부터 9까지의 정수를 활용해 k+1개의 정수를 골라야하고, 이때 순서가 중요하기 때문에 python에서 순열을 구할 수 있는 itertools.permutations() 함수를 활용했다.
부등호 사이에 정수를 넣고 참이 나오는 올바른 식인지 확인하기 위해 고심하다 문자열 포매팅을 선택했다. 부등호 입력이 공백을 사이에 둔 형태 < > 와 같이 들어오기 때문에, 그 공백을 {}으로 replace() 함수를 활용해 바꾼 뒤 .fotmat(*cases) 과 같이 정수 순열을 넣어 문자열을 코드로 실행할 수 있는 eval() 함수를 활용해 참인지 거짓인지 판단할 수 있도록 했다.
최댓값과 최솟값만 구하면 되기에 모든 순열을 뒤에서부터 찾는 반복문과 앞에서부터 찾는 반복문을 분리하여 보다 효율적으로 결과값을 탐색할 수 있도록 했다.
하지만 이 코드는 시간 초과와 메모리 초과라는 결과를 얻어, chatGPT를 활용해 아래 코드와 같이 최적화 과정을 거쳤다. (짧은 지식으로 어떻게 고쳐야할지 막막해 도움을 받았다. 😭)
✅ 2차 시도
import itertools
k = int(input())
signs = input().strip().split()
# 부등호 관계 검사
def check(nums):
for i in range(k): # 부등호 위반 여부 판단
if signs[i] == "<" and nums[i] >= nums[i+1]:
return False
if signs[i] == ">" and nums[i] <= nums[i+1]:
return False
return True # 모든 조건 만족
max_ans, min_ans = None, None
for perm in itertools.permutations(range(10), k+1):
if check(perm): # 부등호 관계 만족 여부 체크
num_str = ''.join(map(str, perm))
if min_ans is None:
min_ans = num_str
max_ans = num_str
print(max_ans)
print(min_ans)
먼저, 문자열 포매팅과 eval() 함수를 활용한 부등호 식 검사는 느리며 보안 상의 문제가 있어 시간 초과가 발생함에 있어 주요한 원인이었다. 이때 부등호 식 검사를 직접 수행하는 함수 check() 를 작성하여 파이썬이 제공하는 함수를 사용하지 않고 반복문과 조건문을 활용해 부등호 관계가 적합한지, 참인지 판별할 수 있도록 했다.
메모리 초과가 발생하는 원인은 순열의 결과를 변수로 저장해 사용했기 때문이었다. 예를 들어 k가 9일 경우 10! = 3,628,800개의 순열을 한 번에 메모리에 저장하게 되기 때문에 모든 경우의 수, 즉 모든 순열을 미리 가져오는 것은 메모리 초과 문제를 야기한다. 이 문제를 해결하기 위해서는 미리 가져오는 것이 아닌, for문에 직접 사용하여 반복문이 실행될 때마다 순열을 하나씩 생성하는 방식을 사용했다. 이러한 방식으로 변경하면서 한 번의 반복문으로 최댓값과 최솟값을 함께 찾도록 수정하였다.
위와 같이 수정하면서 원활히 문제를 해결할 수 있었다!
같은 로직이어도 어떻게 구현하느냐에 따라서 결과가 천차만별이 될 수 있음을 새삼스럽게 깨달을 수 있었다. 파이썬의 장점 중 하나는 편리한 내장 함수가 많다는 점이지만, 그 함수만 활용하여 효율적인 코드를 작성하기는 참 어려운 것 같다는 생각이 들었다.
이번에는 eval() 함수와 문자열 포매팅을 활용할 생각에 좀 더 폭넓은 사고를 하지 못한 것 같아 아쉬움이 든다. 결국 정답 코드도 온전히 해결한 것이 아니기 때문에, 다음에 이 문제를 마주하였을 때는 꼭 스스로의 힘으로 문제를 풀 수 있었으면 좋겠다.