https://www.acmicpc.net/problem/1038
프로세스
- 재귀(백트래킹)를 사용해서 9에서 오른쪽 or 0에서 왼쪽으로 감소/증가 하는 방향으로 감소하는 수를 모두 구한다.
- 오름차순으로 정렬한다.
- n > 1023이라면 -1을, 그렇지 않다면 n번째 수를 출력한다.
풀이
처음엔 앞에 있는 수로 다음 수를 구하는 것 같아서 DP를 떠올렸지만 경우의 수가 아닌 n번째 수를 구하는 방법을 생각해내지 못했다.
다음은 완전 탐색으로 풀 수 있는지 생각해본다. 수의 범위: 0 ~ 9876543210, 하지만 이 숫자를 전부 확인하면 시간 초과다.
어떻게 해야할까
각 자릿수는 일정한 방향성을 갖는다. 왼쪽의 수가 7이라면 오른쪽에 올 수 있는 수는 6~0이다. 이 아이디어에 착안하면 재귀적으로 감소하는 수만 골라서 모두 구할 수 있다. 그리고 이것을 정렬하면 n번째 감소하는 수를 구할 수 있다.
재귀 함수의 구체적인 구현은 다음과 같다
1. 이전 수의 마지막 자리 숫자보다 작은 수를 오른쪽에 추가해서 새로운 숫자를 구한다. -> 나머지 연산 사용
2. 새로 구한 숫자로 재귀 함수를 호출한다
for (i in lastDigit - 1 downTo 0) {
val newNumber = number * 10 + i // 오른쪽에 감소하는 수 추가
addDecreasingNumber(newNumber)
}
3. 호출된 함수는 숫자를 감소하는 수에 추가한다
4. 마지막 자리 수가 0이라면 재귀 함수를 종료한다.
재귀 함수 소스 코드
fun addDecreasingNumber(number: Long) {
decreasingNumbers.add(number)
val lastDigit = number % 10
if (lastDigit <= 0) {
return
}
for (i in lastDigit - 1 downTo 0) {
val newNumber = number * 10 + i
addDecreasingNumber(newNumber)
}
}
위 방식은 왼쪽에서 오른쪽으로 숫자를 추가하면서 감소하는 방향으로 수를 구하고 있다. 반대로, 오른쪽에서 증가하는 방향으로 왼쪽에 숫자를 추가하는 방식도 가능하다.
감소하는 수를 구할 수 있을지 없을지는 어떻게 판단할까?
9 8 7 6 5 4 3 2 1 0에서 9531을 도출하는 방법을 경우의 수 관점에서 생각해볼 수 있다.
9, 5, 3, 1을 선택, 8, 7, 6, 4, 2, 0은 선택하지 않는 것이다. 각 자릿수를 선택/선택하지 않는 모든 경우의 수는 2^10개이고, 모두 선택하지 않는 경우는 없기 때문에 2^10 - 1 = 1023 개가 나온다. 따라서, n > 1022라면 감소하는 수를 구할 수 없다.
코드 (Kotlin)
1. 감소하는 방향으로 백트래킹
/**
* 1. 백트래킹으로 0 ~ 9876543210까지 감소하는 수를 구한다
* 2. 정렬한다
* 3. n번째 값을 출력한다
* 4. 정수 범위에 주의한다
*/
import java.io.BufferedReader
import java.io.InputStreamReader
import kotlin.math.pow
val decreasingNumbers = mutableListOf<Long>()
fun main() = with(BufferedReader(InputStreamReader(System.`in`))) {
val targetIndex = readLine().toInt()
if (targetIndex <= 10) {
println(targetIndex)
} else if (targetIndex >= 1023){ // 감소하는 수의 개수는 1023개다
println(-1)
} else {
for (i in 0..9) {
addDecreasingNumber(i.toLong())
}
decreasingNumbers.sort()
println(decreasingNumbers[targetIndex])
}
}
fun addDecreasingNumber(number: Long) {
decreasingNumbers.add(number)
val lastDigit = number % 10
if (lastDigit <= 0) {
return
}
for (i in lastDigit - 1 downTo 0) {
val newNumber = number * 10 + i
addDecreasingNumber(newNumber)
}
}
2. 증가하는 방향
/**
* 1. 백트래킹으로 0 ~ 9876543210까지 감소하는 수를 구한다
* 2. 정렬한다
* 3. n번째 값을 출력한다
* 4. 정수 범위에 주의한다
*/
import java.io.BufferedReader
import java.io.InputStreamReader
import kotlin.math.pow
val decreasingNumbers = mutableListOf<Long>()
fun main() = with(BufferedReader(InputStreamReader(System.`in`))) {
val targetIndex = readLine().toInt()
if (targetIndex <= 10) {
println(targetIndex)
} else {
addDecreasingNumber(0, 0, 0)
decreasingNumbers.sort()
if (targetIndex > decreasingNumbers.lastIndex) {
println(-1)
} else {
println(decreasingNumbers[targetIndex])
}
}
}
fun addDecreasingNumber(startNumber: Int, beforeLength: Int, beforeNumber: Long) {
if (beforeLength >= 10 || startNumber >= 10) {
return
}
for (i in startNumber..9) {
val newNumber = i * (10.0).pow(beforeLength).toLong() + beforeNumber
decreasingNumbers.add(newNumber)
addDecreasingNumber(i + 1, beforeLength + 1, newNumber)
}
}
'Problem Solving > 백준' 카테고리의 다른 글
[Kotlin] 백준 2015번 - 수들의 합4 (0) | 2022.05.18 |
---|---|
[Kotlin] 백준 22865번 - 가장 먼 곳 (0) | 2022.05.17 |
백준 14888번: 연산자 끼워넣기 (Kotlin) (0) | 2022.03.31 |
백준 13397번: 구간 나누기 2 (Kotlin) (0) | 2022.02.12 |
백준 2262번: 토너먼트 만들기 (Kotlin) (0) | 2022.01.27 |