25.09.12 젬미니한테 코드 작성 부탁함 네, 안녕하세요! 백준 온라인 저지 1038번 ‘감소하는 수’ 문제의 자바(Java) 풀이를 자세한 주석과 함께 설명해 드리겠습니다.
1. 문제 분석 및 핵심 아이디어
감소하는 수란?
- 문제에서 정의한 “감소하는 수”는 각 자릿수가 왼쪽에서 오른쪽으로 갈수록 엄격하게 작아지는 수를 의미합니다.
- 예시: 321, 950, 5, 0
- 아닌 예시: 322 (2가 반복됨), 123 (증가함)
문제의 목표
- N이 주어졌을 때, 0번째부터 시작하는 N번째 감소하는 수를 찾는 것입니다.
- 만약 N번째 감소하는 수가 존재하지 않으면 -1을 출력합니다.
핵심 아이디어
-
단순 반복의 한계: 0부터 모든 수를 하나씩 검사하며 감소하는 수인지 확인하는 방법(브루트포스)은 비효율적입니다. N이 커지면 찾아야 할 수도 매우 커져서 시간 초과가 발생합니다. 예를 들어, 1,000,000번째 감소하는 수를 찾기 위해 수억, 수십억 개의 숫자를 검사해야 할 수도 있습니다.
-
생성의 관점: 모든 수를 검사하는 대신, 감소하는 수만 직접 생성하는 방법을 생각할 수 있습니다.
- 감소하는 수는 어떻게 만들 수 있을까요?
- 기존 감소하는 수의 마지막 자릿수보다 더 작은 숫자를 뒤에 붙이면 새로운 감소하는 수를 만들 수 있습니다.
- 예:
32는 감소하는 수입니다. 마지막 자릿수는2입니다.2보다 작은0,1을 뒤에 붙여320,321이라는 새로운 감소하는 수를 만들 수 있습니다.
-
재귀(DFS) 또는 BFS 활용: 이 생성 과정은 깊이 우선 탐색(DFS)이나 너비 우선 탐색(BFS)으로 구현하기에 아주 적합합니다. 한 자리 수(0~9)에서 시작하여 점차 자릿수를 늘려가며 모든 감소하는 수를 생성할 수 있습니다.
-
감소하는 수의 개수는 유한하다:
- 감소하는 수는 0~9까지의 숫자를 중복 없이 사용해서 만듭니다. (예: 321은 {1, 2, 3} 집합으로 만듦)
- 가장 큰 감소하는 수는 9, 8, 7, 6, 5, 4, 3, 2, 1, 0을 모두 사용한
9876543210입니다. - 총 감소하는 수의 개수는 10개의 숫자(0~9)로 만들 수 있는 모든 부분집합의 개수(공집합 제외)와 같습니다. 즉, 2^10 - 1 = 1023개 입니다.
- 따라서 0번째부터 시작하면 1022번째 감소하는 수(
9876543210)가 마지막입니다. 만약N > 1022이면 답은 무조건 -1이 됩니다. 이 사실을 이용하면 불필요한 계산을 줄일 수 있습니다.
2. 알고리즘 설계 (DFS 방식)
- 모든 감소하는 수를 저장할
ArrayList<Long>을 준비합니다. (long타입을 사용하는 이유는 가장 큰 감소하는 수가Integer범위를 초과하기 때문입니다.) - 재귀 함수
generate(long number)를 만듭니다.- 함수가 호출되면, 현재
number를 리스트에 추가합니다. - 현재
number의 마지막 자릿수를 구합니다. 0부터(마지막 자릿수 - 1)까지의 숫자를 현재number뒤에 붙여 새로운 수를 만듭니다.- 만들어진 새로운 수에 대해
generate()함수를 재귀적으로 호출합니다.
- 함수가 호출되면, 현재
- 메인 로직에서는 한 자리 수인 0부터 9까지를 시작으로
generate()함수를 각각 호출하여 모든 감소하는 수를 생성합니다. - 모든 수가 생성된 후, 리스트를 오름차순으로 정렬합니다. (재귀 호출 순서가 수의 크기 순서가 아니기 때문입니다.)
- 정렬된 리스트에서
N번째 인덱스의 값을 찾아 출력합니다. 만약N이 리스트의 크기보다 크거나 같다면, 해당하는 수가 없으므로 -1을 출력합니다.
3. Java 전체 코드 (자세한 주석 포함)
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
/**
* 백준 1038번: 감소하는 수
* https://www.acmicpc.net/problem/1038
*/
public class Main {
// 생성된 모든 감소하는 수를 저장할 리스트 (static으로 선언하여 재귀함수에서 쉽게 접근)
static ArrayList<Long> list;
public static void main(String[] args) throws IOException {
// --- 입력 처리 ---
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int N = Integer.parseInt(br.readLine());
// --- 초기화 ---
list = new ArrayList<>();
// --- 예외 처리: N번째 감소하는 수가 존재할 수 없는 경우 ---
// 감소하는 수는 총 1023개 (0부터 9876543210까지) 존재합니다.
// 인덱스는 0부터 시작하므로, 1022번째가 마지막 감소하는 수입니다.
// 따라서 N이 1022보다 크면, N번째 감소하는 수는 존재하지 않으므로 -1을 출력합니다.
if (N > 1022) {
System.out.println(-1);
return;
}
// --- 감소하는 수 생성 (DFS) ---
// 0부터 9까지의 수로 시작하여 감소하는 수를 재귀적으로 생성합니다.
for (int i = 0; i < 10; i++) {
generateNumber(i);
}
// --- 정렬 ---
// 생성된 수들이 순서대로 리스트에 추가되지 않았으므로, 오름차순으로 정렬합니다.
// 예: generate(2)가 generate(10)보다 먼저 호출되므로, 2, 21, 210, 20... 순으로 리스트에 추가될 수 있습니다.
Collections.sort(list);
// --- 출력 ---
// 정렬된 리스트에서 N번째 인덱스에 있는 값을 출력합니다.
System.out.println(list.get(N));
}
/**
* 감소하는 수를 생성하는 재귀 함수 (DFS 방식)
* @param num 현재까지 만들어진 감소하는 수
*/
public static void generateNumber(long num) {
// 1. 현재 수를 리스트에 추가합니다.
list.add(num);
// 2. 현재 수의 마지막 자릿수를 구합니다. (예: 32 -> 2)
long lastDigit = num % 10;
// 3. 마지막 자릿수보다 작은 수를 뒤에 붙여 새로운 감소하는 수를 만듭니다.
// 예: num이 32라면, lastDigit은 2. 2보다 작은 0, 1을 순서대로 뒤에 붙여 320, 321을 생성합니다.
for (int i = 0; i < lastDigit; i++) {
// 새로운 수 생성: (현재 수 * 10) + 다음 자릿수
// 예: 32 * 10 + 1 = 321
long newNum = (num * 10) + i;
// 만들어진 새로운 수를 기준으로 재귀 호출을 통해 계속해서 다음 감소하는 수를 생성합니다.
generateNumber(newNum);
}
}
}
재재출한 코드
import java.io.*;
import java.util.*;
class Main{
static int n;
static List<Long> list = new ArrayList<>();
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
n = Integer.parseInt(br.readLine());
//0 1 2 3 4 5
//6 7 8 9 10
//20 21 30 31 32 40
//40 41 42(18번째) 43 50
//감소하는 수는 총 1023 0~9876543210
//index는 0부터니까 1022가 마지막 감소하는 수
if(n > 1022) {
System.out.print(-1);
return;
}
//반복하는 수 재귀로 생성한다.
for(int i=0;i<10;i++){
dfs(i);
}
Collections.sort(list);
System.out.print(list.get(n));
}
static void dfs(long num){
list.add(num);
//현재 수의 마지막 자릿수(예: 32 -> 2)
long lastDigit = num % 10;
//마지막 자리수보다 작은 수를 뒤에 붙여 감소하는 수 새로 만든다.
//32라면 lastDigit = 2이므로 2보다 작은 1,0을 뒤에 붙여 320, 321 을 생성한다.
for(int i=0 ; lastDigit>i;i++){
//32 * 10 + 1 = 321
long newNum = (num * 10) + i;
dfs(newNum);
}
}
}