Notice
Recent Posts
Link
반응형
All :L
[BOJ/Java] 알파벳 (1987) 본문
반응형
1. 문제 분석
문제 개요
- 이 문제는
R x C
크기의 보드에서 알파벳이 적힌 칸을 이동하며 최대 경로 길이를 구하는 문제이다. 각 칸에는 A부터 Z까지의 대문자 알파벳이 적혀 있으며, 한 번 방문한 알파벳이 적힌 칸은 다시 방문할 수 없다. - 출발점은 좌측 상단의
(0, 0)
에서 시작하며, 상하좌우로 인접한 칸으로만 이동할 수 있다.
목표
- 중복되지 않는 알파벳을 최대한 많이 방문하면서 이동할 수 있는 경로의 최대 길이를 구하는 것이다.
입력 형식
- 첫 줄에 보드의 크기
R
과C
가 주어진다. - 이후
R
줄에 걸쳐 각 줄마다C
개의 대문자 알파벳이 주어진다.
출력 형식
- 가능한 최대 경로 길이를 출력한다.
2. 알고리즘 종류
이 문제는 "백트래킹을 이용한 깊이 우선 탐색(DFS)" 문제이다. DFS를 활용하여 경로를 탐색하고, 최대 경로 길이를 갱신한다.
3. 주요 부분 및 코드 작성 방법
1. DFS 탐색을 위한 방향 배열 설정
- 상하좌우로 이동하기 위한 방향 배열
dr
과dc
를 설정한다.
2. 방문 배열 및 체크 세트 초기화
visited
배열을 통해 칸을 방문했는지 추적한다.checked
세트를 사용하여 이미 방문한 알파벳을 기록한다.
3. DFS 구현
- 현재 위치에서 시작해 상하좌우로 이동하며 가능한 경로를 모두 탐색한다.
- 이동할 때마다 현재 위치에서 방문할 수 있는 최대 경로 길이를 갱신한다.
4. 결과 출력
- 최대 경로 길이를 출력한다.
4. 코드 설명
DFS 탐색 함수 (dfs()
)
public static void dfs(int r, int c, int depth) { for (int i = 0; i < 4; i++) { // 4방향으로 탐색 int nr = r + dr[i]; // 새로운 r 좌표 int nc = c + dc[i]; // 새로운 c 좌표 if(nr < 0 || nc < 0 || nr >= R || nc >= C) continue; // 범위를 벗어난 경우 건너뜀 if(!checked.contains(board[nr][nc])) { // 아직 방문하지 않은 알파벳인 경우 checked.add(board[nr][nc]); // 해당 알파벳 추가 visited[nr][nc] = true; // 해당 위치 방문 처리 dfs(nr, nc, depth + 1); // 재귀적으로 탐색 visited[nr][nc] = false; // 백트래킹 checked.remove(board[nr][nc]); // 알파벳 제거 } } max = Math.max(max, depth); // 최대 경로 길이 갱신 }
- 현재 위치에서 4방향으로 이동하며 DFS를 수행한다.
- 방문 가능한 경우
checked
와visited
에 기록하고, 재귀적으로 다음 위치로 이동한다. - 이동이 끝나면 백트래킹을 통해 상태를 복원한다.
메인 함수 (main()
)
public static void main(String[] args) throws Exception { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); StringTokenizer st = new StringTokenizer(br.readLine()); R = Integer.parseInt(st.nextToken()); // 보드의 행 크기 입력 C = Integer.parseInt(st.nextToken()); // 보드의 열 크기 입력 board = new char[R][C]; // 보드 배열 초기화 visited = new boolean[R][C]; // 방문 체크 배열 초기화 for(int i = 0; i < R; i++) board[i] = br.readLine().toCharArray(); // 보드 정보 입력 visited[0][0] = true; // 시작점 방문 처리 checked.add(board[0][0]); // 시작점 알파벳 체크 dfs(0, 0, 1); // DFS 시작 System.out.println(max); // 결과 출력 }
- 보드의 크기와 알파벳 정보를 입력받아 초기화한다.
- 시작점에서 DFS를 호출하여 가능한 최대 경로 길이를 탐색한다.
- 탐색이 완료되면 최대 경로 길이를 출력한다.
5. 전체 코드
package BOJ.BOJ1987_240825; import java.io.*; import java.util.*; public class Ex { static int R, C, max = 0; // 보드 크기와 최대 경로 길이 static int[] dr = {-1, 1, 0, 0}; // 방향 벡터 (상하좌우) static int[] dc = {0, 0, -1, 1}; // 방향 벡터 (상하좌우) static char[][] board; // 보드 배열 static boolean[][] visited; // 방문 체크 배열 static Set<Character> checked = new HashSet<>(); // 방문한 알파벳 체크 public static void main(String[] args) throws Exception { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); StringTokenizer st = new StringTokenizer(br.readLine()); R = Integer.parseInt(st.nextToken()); // 보드의 행 크기 입력 C = Integer.parseInt(st.nextToken()); // 보드의 열 크기 입력 board = new char[R][C]; // 보드 배열 초기화 visited = new boolean[R][C]; // 방문 체크 배열 초기화 for(int i = 0; i < R; i++) board[i] = br.readLine().toCharArray(); // 보드 정보 입력 visited[0][0] = true; // 시작점 방문 처리 checked.add(board[0][0]); // 시작점 알파벳 체크 dfs(0, 0, 1); // DFS 시작 System.out.println(max); // 결과 출력 } public static void dfs(int r, int c, int depth) { for (int i = 0; i < 4; i++) { // 4방향으로 탐색 int nr = r + dr[i]; // 새로운 r 좌표 int nc = c + dc[i]; // 새로운 c 좌표 if(nr < 0 || nc < 0 || nr >= R || nc >= C) continue; // 범위를 벗어난 경우 건너뜀 if(!checked.contains(board[nr][nc])) { // 아직 방문하지 않은 알파벳인 경우 checked.add(board[nr][nc]); // 해당 알파벳 추가 visited[nr][nc] = true; // 해당 위치 방문 처리 dfs(nr, nc, depth + 1); // 재귀적으로 탐색 visited[nr][nc] = false; // 백트래킹 checked.remove(board[nr][nc]); // 알파벳 제거 } } max = Math.max(max, depth); // 최대 경로 길이 갱신 } }
6. 시간 초과
시간 초과 코드
package BOJ.BOJ1987_240825; import java.io.*; import java.util.*; public class Main { static int R, C, max = 0; static int[] dr = {-1, 1, 0, 0}; static int[] dc = {0, 0, -1, 1}; static String[][] board; static boolean[][] visited; static List<String> checked = new ArrayList<>(); public static void main(String[] args) throws Exception { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); StringTokenizer st = new StringTokenizer(br.readLine()); R = Integer.parseInt(st.nextToken()); C = Integer.parseInt(st.nextToken()); board = new String[R][C]; visited = new boolean[R][C]; for(int i = 0; i < R; i++) board[i] = br.readLine().split(""); visited[0][0] = true; checked.add(board[0][0]); dfs(0, 0, 1); System.out.println(max); } public static void dfs(int r, int c, int depth) { for (int i = 0; i < 4; i++) { int nr = r + dr[i]; int nc = c + dc[i]; if(nr < 0 || nc < 0 || nr >= R || nc >= C) continue; if(!checked.contains(board[nr][nc])) { checked.add(board[nr][nc]); visited[nr][nc] = true; dfs(nr, nc, depth + 1); visited[nr][nc] = false; checked.remove(board[nr][nc]); } } max = Math.max(max, depth); } }
차이점
- 자료구조의 차이:
- 첫 번째 코드에서는
checked
리스트를 사용하여 방문한 알파벳을 저장하고 확인한다. - 두 번째 코드에서는
checked
집합(Set)을 사용하여 방문한 알파벳을 저장하고 확인한다.
- 첫 번째 코드에서는
- 탐색 효율성:
- 첫 번째 코드의
List<String> checked
는 리스트를 사용하므로, 알파벳이 이미 방문했는지 확인하는 데 O(N)의 시간이 걸린다. 이는 DFS 탐색에서 각 경로마다checked.contains()
를 여러 번 호출하므로, 전체 탐색 시간이 매우 길어질 수 있다. - 반면, 두 번째 코드의
Set<Character> checked
는 집합을 사용하므로, 알파벳이 이미 방문했는지 확인하는 데 O(1)의 시간이 걸린다. 이는 DFS 탐색에서 훨씬 빠르게 동작하게 되어, 시간 초과 없이 문제를 해결할 수 있다.
- 첫 번째 코드의
시간 초과의 원인
해당 코드가 시간 초과가 발생하는 주된 이유는 checked.contains()
호출 시 리스트에서 중복 여부를 확인하는 데 걸리는 시간이 크다. 리스트는 순차 검색을 하기 때문에, 리스트의 크기가 커지면 커질수록 이 연산이 느려지게 된다. 이로 인해 DFS의 시간 복잡도가 증가하여, 탐색 시간이 과도하게 길어진다.
반면, 제출 코드에서는 집합(Set)을 사용하여 중복을 확인하는 연산이 매우 빠르게 이루어지므로, 동일한 탐색을 훨씬 효율적으로 수행할 수 있다.
결론
- 자료구조 선택: 리스트(List)보다 집합(Set)이 중복 확인 작업에서 훨씬 효율적이다.
- 탐색 시간 최적화: 자료구조의 선택에 따라 탐색의 시간 복잡도가 크게 달라질 수 있다.
따라서 리스트 대신 집합을 사용하면, 시간 초과 문제를 해결할 수 있을 것이다.
반응형
'CODING > BOJ' 카테고리의 다른 글
[BOJ/Java] 괄호 (9012) (0) | 2024.08.26 |
---|---|
[BOJ/Java] 스택 (10828) (0) | 2024.08.26 |
[BOJ/Java] 적록색약 (10026) (0) | 2024.08.25 |
[BOJ/Java] 도키도키 간식드리미 (12789) (0) | 2024.08.25 |
[BOJ/Java] 비밀번호 찾기 (17219) (0) | 2024.08.25 |