15948. 기념품 수집
Code Battle ('23 동계 대학생 S/W 알고리즘 특강 사전문제 (1/7(토) 09:00 ~ 1/11(수) 23:50))
본 게시글은 개인 학습 용도로 작성한 게시글입니다.
(문제 출처: SW Expert Academy)
- 가장 긴 경로를 찾는 문제.
- 노드의 종류가 최대 알파벳 개수이다(26개).
- DFS를 이용하여 풀이.
- 노드의 수가 한정되어 있으므로 방문체크는 Boolean으로 이루어진 배열을 선언하여 사용하며, 각 노드에 대응하는 원소에 임의 접근하여 $O(1)$시간에 검사할 수 있다.
- 하지만 위의 방법은 알파벳을 인덱스로 변환하기 위해
char
을int
로 매번 캐스트 해줘야하므로 번거롭다.HashMap<Character, Boolean>
으로 대체하여 사용하였다.
- 시간 복잡도는 최악의 경우 $T(3^{25})$이다.1
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.function.Supplier;
public class Solution {
public static void main(String[] args) throws Exception {
new Solution();
}
private final Boolean USE_FILE_INPUT = false;
private BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
private BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(System.out));
private Solution() throws IOException {
if (USE_FILE_INPUT) {
try {
bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream("./input.txt")));
} catch (FileNotFoundException e) { }
}
solve();
close();
}
private void close() throws IOException {
bufferedReader.close();
bufferedWriter.close();
}
private void solve() throws IOException {
int T = Integer.parseInt(bufferedReader.readLine());
for (Integer testCaseNo = 1; testCaseNo <= T; testCaseNo++) {
bufferedWriter.write("#"+testCaseNo+" "+solveTestcase()+"\n");
}
}
private Integer solveTestcase() throws IOException {
StringTokenizer stringTokenizer = new StringTokenizer(bufferedReader.readLine());
int R = Integer.parseInt(stringTokenizer.nextToken());
int C = Integer.parseInt(stringTokenizer.nextToken());
char[][] cities = new char[R][C];
for (int r = 0; r < R; r++) {
String line = bufferedReader.readLine();
for (int c = 0; c < C; c++) {
cities[r][c] = line.charAt(c);
}
}
return solveTestcaseDFS(R, C, cities);
}
/**
* 기념품(알파벳)을 하나의 노드로 보고, (1,1) 도시를 루트로 갖는 트리를 구성한다.
* 루트에서 탐색을 시작하여 방문체크를 하였을 때, 중복되지 않은 노드로 이루어진 경로 중 가장 긴 경로의 크기를 찾는다.
* @return 발견한 가장 긴 경로의 크기를 {@code Integer}로 반환한다.
* @throws IOException
*/
private Integer solveTestcaseDFS(int height, int width, char[][] cities) {
Map<Character, Boolean> visited = new HashMap<>();
return solveTestcaseDFSRecursive(height, width, 0, 0, cities, visited);
}
private Integer solveTestcaseDFSRecursive(int height, int width, int r, int c, char[][] cities, Map<Character, Boolean> visited) {
if (!isValidRange(height, width, r, c) || isVisited(r, c, cities, visited)) {
return 0;
}
return 1 + findMax(
markVisitedThen(r, c, cities, visited, () -> solveTestcaseDFSRecursive(height, width, r+1, c, cities, visited)),
markVisitedThen(r, c, cities, visited, () -> solveTestcaseDFSRecursive(height, width, r-1, c, cities, visited)),
markVisitedThen(r, c, cities, visited, () -> solveTestcaseDFSRecursive(height, width, r, c+1, cities, visited)),
markVisitedThen(r, c, cities, visited, () -> solveTestcaseDFSRecursive(height, width, r, c-1, cities, visited))
);
}
private Boolean isValidRange(int height, int width, int r, int c) {
return r >= 0 && r < height && c >= 0 && c < width;
}
private Boolean isVisited(int r, int c, char[][] cities, Map<Character, Boolean> visited) {
return visited.getOrDefault(cities[r][c], false);
}
private <T> T markVisitedThen(int r, int c, char[][] cities, Map<Character, Boolean> visited, Supplier<T> callback) {
visited.put(cities[r][c], true);
T retval = callback.get();
visited.put(cities[r][c], false);
return retval;
}
private Integer findMax(Integer ...args) {
return Arrays.asList(args).stream().reduce(Math::max).get();
}
}
상하좌우 4방위로 이동할 수 있으나 왔던 곳으로 돌아갈 수는 없다. 시작점은 고정이고, 3방위로 밖에 이동할 수 없는데다, 알파벳은 26자가 있으므로 (최대 25번 이동) x (3 방위). ↩