코딩테스트 준비할 때 자잘한 처리에 도움이 될 것 같아서 남기는 포스팅이다. 어렵지 않은 문제로, 알고리즘을 공부한 사람들이라면 BFS로의 접근을 쉽게 떠올릴 수 있는 문제였다.
https://www.acmicpc.net/problem/25418
문제
입력으로 양의 정수 A와 K가 주어지면, 아래 연산을 이용하여 A를 K로 변경하려고 한다. 정수 A를 변경할 때 사용할 수 있는 연산 종류는 다음과 같다.
- 연산 1: 정수 A에 1을 더한다.
- 연산 2: 정수 A에 2를 곱한다.
정수 A를 정수 K로 만들기 위해 필요한 최소 연산 횟수를 출력하자.
입력
첫 번째 줄에 양의 정수 A와 K가 빈칸을 사이에 두고 순서대로 주어진다.
출력
첫 번째 줄에 양의 정수 A를 양의 정수 K로 만들기 위해 필요한 최소 연산 횟수를 출력한다.
제한
1 ≤ A < K ≤ 1,000,000
예제 입력
1111 997651
예제 출력
850
나 또한 처음엔 이 문제를 보자마자 BFS를 떠올렸고, 코드를 다음과 같이 작성했다.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.Queue;
import java.util.StringTokenizer;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
int A = Integer.parseInt(st.nextToken());
int K = Integer.parseInt(st.nextToken());
int[] visited = new int[K*2+1];
Queue<Integer> q = new LinkedList<>();
visited[A] = 1;
q.offer(A);
while (!q.isEmpty()) {
int cur = q.poll();
if (cur == K) {
System.out.println(visited[cur]-1);
break;
}
// 정수 A에 1을 더한다
if (visited[cur+1] == 0 && cur+1 <= K) {
visited[cur+1] = visited[cur] + 1;
q.offer(cur+1);
}
// 정수 A에 2를 곱한다
if (visited[cur*2] == 0 && cur*2 <= K) {
visited[cur*2] = visited[cur] + 1;
q.offer(cur*2);
}
}
}
}
제한으로 A < K 라는 조건이 있었고, A가 K-1이 되는 최악의 경우를 생각해 연산 횟수와 방문 여부를 통합 관리하는 visited 배열의 크기는 K*2+1로 설정해주었다.
무난하게 맞췄으나, 이 코드를 개선해보고자 했다. K 이상의 숫자들에 대해서도 큐에 넣어 확인하는 것이 시간, 메모리적으로 비효율적이라고 생각했다. 나는 시간과 메모리 비용이 상호 보완적 관계에 있다고 생각하는데, 이 코드는 메모리적으로 보나 시간적으로 보나 개선될 여지가 있었다. 그래서 다시 작성한 코드이다.
정답 코드
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.Queue;
import java.util.StringTokenizer;
public class boj25418 {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
int A = Integer.parseInt(st.nextToken());
int K = Integer.parseInt(st.nextToken());
int[] visited = new int[K+1];
Queue<Integer> q = new LinkedList<>();
visited[K] = 1;
q.offer(K);
while (!q.isEmpty()) {
int cur = q.poll();
if (cur == A) {
System.out.println(visited[cur]-1);
break;
}
if (visited[cur-1] == 0 && cur-1 >= A) {
visited[cur-1] = visited[cur] + 1;
q.offer(cur-1);
}
if (visited[cur/2] == 0 && cur%2 == 0 && cur/2 >= A) {
visited[cur/2] = visited[cur] + 1;
q.offer(cur/2);
}
}
}
}
주어지는 연산 자체가 더하기와 곱하기이고 A와 K가 둘 다 양수라는 조건이 있었으니, A -> K로 가는 과정이 단방향이라고 생각했다. 이를 역으로 생각해보면 K라는 숫자에서 반대 연산을 하게 되어도 K -> A로 가는 과정이 단방향이 되는 것이다. 그렇다면 배열의 크기를 K 이상으로 잡을 필요가 없어진다.
A -> K가 되지 않는 경우는 주어지지 않는 문제이므로 K -> A가 되는 과정에서 배열 인덱스가 음수를 가리키게 되는 상황도 없을 것이라고 판단해서, K -> A로 연산을 해 나가도록 구현해보았다. (사실 부트캠프에서 알고리즘 문제를 풀 때 페어 오빠가 친절하게 알려주셔서 생각해뒀던 아이디어인데 이렇게 써먹게 되었다... ㅎㅎ감사합니다)
int[] visited = new int[K+1];
개선점을 살펴보면, visited 배열의 크기가 절반으로 줄었다. 이를 통해 메모리 비용이 줄어들 것임을 예측할 수 있다.
if (visited[cur-1] == 0 && cur-1 >= A) {
visited[cur-1] = visited[cur] + 1;
q.offer(cur-1);
}
if (visited[cur/2] == 0 && cur%2 == 0 && cur/2 >= A) {
visited[cur/2] = visited[cur] + 1;
q.offer(cur/2);
}
while 문의 내용을 보면 더하기, 곱하기를 빼기, 나누기로 변환해 작성한 것을 알 수 있다. 추가된 것은 나누기 연산을 할 때 cur (현재 숫자) 값이 짝수인 지를 확인하는 부분인데, 홀수를 2로 나눠서 연산 오류가 발생하는 것을 막아준 것이다. Java 에서는 15 / 2 의 연산이 7로 처리되기 때문에 이 부분을 고려해줬다.
제출 결과, 이전 코드에 비해 메모리 사용량은 53712 KB -> 25276 KB로 줄었고, 시간 또한 136 ms -> 112 ms로 줄어든 것을 볼 수 있다.
'Algorithm' 카테고리의 다른 글
[백준] 1024 수열의 합 Java (0) | 2025.03.07 |
---|---|
[백준] 5427 불 Java (0) | 2025.02.27 |
[백준] 13913 숨바꼭질 4 Java : 경로 역추적 (0) | 2025.02.18 |
[백준] 12851 숨바꼭질 2 Java (0) | 2025.02.17 |
[백준] 3085 사탕 게임 Java (0) | 2025.02.10 |