Skip to main content Link Menu Expand (external link) Document Search Copy Copied

15948. 기념품 수집

Code Battle ('23 동계 대학생 S/W 알고리즘 특강 사전문제 (1/7(토) 09:00 ~ 1/11(수) 23:50))


본 게시글은 개인 학습 용도로 작성한 게시글입니다.

(문제 출처: SW Expert Academy)


  • 가장 긴 경로를 찾는 문제.
  • 노드의 종류가 최대 알파벳 개수이다(26개).
  • DFS를 이용하여 풀이.
    • 노드의 수가 한정되어 있으므로 방문체크는 Boolean으로 이루어진 배열을 선언하여 사용하며, 각 노드에 대응하는 원소에 임의 접근하여 $O(1)$시간에 검사할 수 있다.
    • 하지만 위의 방법은 알파벳을 인덱스로 변환하기 위해 charint로 매번 캐스트 해줘야하므로 번거롭다. 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();
    }
}
  1. 상하좌우 4방위로 이동할 수 있으나 왔던 곳으로 돌아갈 수는 없다. 시작점은 고정이고, 3방위로 밖에 이동할 수 없는데다, 알파벳은 26자가 있으므로 (최대 25번 이동) x (3 방위). 


Back to top