[Gold V] 리모컨 - 1107
성능 요약
메모리: 11696 KB, 시간: 96 ms
주어진 M 개 숫자들을 제외하고 0 ~ 9까지의 숫자들과 +1, -1 연산을 사용하여 숫자 100에서 N으로 가기 위해 이동 해야 하는 최소 횟수를 구하는 문제
⭕ 접근 방법. 백트래킹
만약 시작점이 0이라면
고장나지 않은 숫자 버튼을 이용해 목표 채널까지 도달 할 수 있는 가장 가까운 채널로 이동 먼저 한 후 +1 , -1 둘 중 하나의 버튼 만을 이용해 쭉 이동하여 목표채널에 도달하는 게 가장 짧은 횟수로 이동하는 방법이다.
그러나 문제에서는 시작점이 100이기 때문에 숫자 버튼을 이용해서 가장 가까운 채널로 이동 후 움직이는 것보다 100에서 바로 움직이는 게 더 짧은 횟수를 가질 수도 있다. (ex) 99로 이동할때 9버튼이 고장난 경우
그래서 100에서 부터 목표 채널까지의 거리(+1 또는 -1로만 이동하는 횟수)를 먼저 고려한 후
도달 할 수 있는 모든 채널을 탐색하여 최소 버튼 사용 횟수를 비교하여 최소인 값을 출력한다.
탐색 범위는 가장 큰 목표 채널의 번호는 500,000 이기 때문에 1,000,000 -100 까지만 탐색하면 된다.
그 이유는 500,000이 목표 채널이라고 할 때 100에서 +1로 올라가 목표 채널 까지 이동하는 횟수와 1,000,000 - 100 에서 -1 로 내려 와 목표 채널까지 이동 횟수가 동일하기 때문이다.
목표 채널이 최대 500,000 일때 이동 횟수가 500,000-100보다 더 커지는 경우는 없기 때문에 1,000,000 그 이상의 채널에서 +1, -1로 이동해서 내려오는 경우는 생각하지 않아도 된다.
그러나 999,900 과 999,999 의 시간 복잡도는 크게 차이나지 않기 때문에 탐색 범위를 999,999로 생각하자.
그렇기 때문에 최대 탐색 숫자는 999, 999 이고 숫자 버튼을 누를 수 있는 횟수는 최대 6번이기에 종료조건은 숫자 버튼을 6번 눌렀을 경우 이다.
➡️ 해당 풀이법의 시간 복잡도 :
기존 코드 ( 매우 지저분하니 보지 말자.. )
private static void makeChannel(int depth, int click) {
if(click >= min) return;
if(depth == 6){
if(channel == 0){
if(isBroken[0]) return;
click +=1;
}
min = Math.min(min, click+Math.abs(N - channel));
return;
}
for (int i = 0; i < 10; i++) {
if(i != 0 && isBroken[i]) continue;
if(i == 0 && isBroken[0]){
if(channel != 0) continue;
}
channel = channel*10 +i;
if( N > 100 && channel>N*2){
break;
}
makeChannel(depth+1, channel == 0 ? 0 : click+1);
channel = channel/10;
}
}
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
public class Main_1107 {
// N: 수빈이가 이동하려는 채널
static int N;
// M: 고장난 버튼의 개수
static int M;
// isBroken: 버튼이 고장났는지 여부를 저장하는 배열
static boolean isBroken[];
// min: 버튼을 최소 몇 번 눌러야 하는지를 저장하는 변수
static int min = Integer.MAX_VALUE;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
// 수빈이가 이동하려는 채널을 입력 받음
N = Integer.parseInt(br.readLine());
// 고장난 버튼의 개수를 입력 받음
M = Integer.parseInt(br.readLine());
// 버튼이 고장나지 않은 경우를 기본으로 설정
isBroken = new boolean[10];
// 만약 고장난 버튼의 개수가 0이 아니라면,
if (M != 0) {
StringTokenizer st = new StringTokenizer(br.readLine());
// 고장난 버튼들을 입력 받아 isBroken 배열에 표시
for (int i = 0; i < M; i++) {
isBroken[Integer.parseInt(st.nextToken())] = true;
}
}
// 현재 채널에서 목표 채널까지의 +1, 또는 -1 로만 이동하는 횟수
min = Math.abs(N - 100);
// 가능한 모든 채널을 탐색하여 최소 버튼 누름 횟수 갱신
makeChannel(0, 0);
// 최소 버튼 누름 횟수 출력
System.out.println(min);
}
// click 번 버튼을 입력하여 channel 번호를 만드는 함수
private static void makeChannel(int click, int channel) {
// 종료 조건: 숫자 버튼을 6번 눌러 채널이 999,999를 넘어가면 종료
if (click == 6) {
return;
}
// 0부터 9까지 버튼을 하나씩 눌러가며 탐색
for (int i = 0; i < 10; i++) {
// 만약 해당 버튼이 고장났다면 스킵
if (isBroken[i])
continue;
// 현재 채널 번호에서 1 ~ 9 까지 수 중 고장나지 않은 버튼을 추가로 눌러서 새 채널 번호를 만듦
int newChannel = channel * 10 + i;
// 목표 채널의 두 배보다 큰 채널은 탐색하지 않음
// 목표 채널의 두배보다 더 크게 되면 100에서 +1, -1로 이동해 목표채널로 가는게 무조건 더 짧기 때문이다.
if (N > 100 && newChannel > N * 2) {
break;
}
// 현재까지 버튼을 누른 횟수와 목표 채널까지의 거리를 계산하여 최소값 갱신
// Math.abs(N - newChannel) 는 +1, -1 버튼으로 이동한 횟수
min = Math.min(min, (click + 1) + Math.abs(N - newChannel));
// 새로운 채널로 재귀 호출하여 버튼을 누르는 과정을 반복
makeChannel(click + 1, newChannel);
}
}
}