문제 풀이/소프티어

[소프티어] [HSAT 1회 정기 코딩 인증평가 기출] 로봇이 지나간 경로

27200 2025. 3. 31. 15:57

문제

https://softeer.ai/practice/6275


풀이(25분)

import java.io.*;
import java.util.*;

public class Main {

    static int h, w;
    // 0 : 동, 1 : 남, 2 : 서, 3 : 북
    // 시계방향 회전(R)은 인덱스 -
    // 반시계방향 회전(L)은 인덱스 +
    static int[] dx = new int[]{0, 1, 0, -1}; // 동 남 서 북
    static int[] dy = new int[]{-1, 0, 1, 0}; // 동 남 서 북
    static final char[] direction = new char[]{'<', 'v', '>', '^'}; // 최종 출력을 위한 화살표
    static char[][] map; // 지도
    static ArrayList<Character> order = new ArrayList<>(); // 길 출력 순서
    static Point endPoint; // 도착점

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st;
        st = new StringTokenizer(br.readLine());
        h = Integer.parseInt(st.nextToken());
        w = Integer.parseInt(st.nextToken());
        map = new char[h][w];
        for(int i = 0; i < h; i++){ // 지도 입력
            String input = br.readLine();
            for(int j = 0; j < w; j++){
                map[i][j] = input.charAt(j);
            }
        }

        List<Point> startPoints = new ArrayList<>(); // 출발점으로 가능한 리스트

        for(int i = 0; i < h; i++){
            for(int j = 0; j < w; j++){
                int count = 0;
                int direction = 0;
                if(map[i][j] == '.'){
                    continue;
                }
                for(int k = 0; k < 4; k++){
                    int nextX = i + dx[k];
                    int nextY = j + dy[k];
                    if(nextX >= 0 && nextX < h && nextY >= 0 && nextY < w){ // 격자 안에 있으며
                        if(map[nextX][nextY] == '#'){ // 다음 이동 방향이 #이 됨
                            count++;
                            direction = k; // 첫번째 #이 이동 방향
                        }
                    }
                }
                if(count == 1){ // 만약 접한게 1개라면
                    startPoints.add(new Point(i, j, direction)); // 시작 리스트에 추가
                }
            }
        }

        endPoint = startPoints.get(1); // 첫 번째 포인트가 시작, 두 번째 포인트가 도착
        dfs(startPoints.get(0)); // 첫 번째 포인트를 기준으로 시작
        ArrayList<Character> firstList = new ArrayList<>(order);
        order.clear();
        endPoint = startPoints.get(0); // 두 번째 포인트가 시작, 첫 번째 포인트가 도착
        dfs(startPoints.get(1)); // 두 번째 포인트를 기준으로 시작
        ArrayList<Character> secondList = new ArrayList<>(order);
        StringBuilder sb = new StringBuilder();
        Point startPoint = null;
        if(firstList.size() > secondList.size()){ // 짧은 것을 찾기
            startPoint = startPoints.get(1);
            order = secondList;
        }else{
            startPoint = startPoints.get(0);
            order = firstList;
        }
        // 형식에 맞는 출력
        System.out.println((startPoint.x+1) + " " + (startPoint.y+1));
        System.out.println(direction[startPoint.direction]);
        for(int i = 0 ; i < order.size(); i++){
            sb.append(order.get(i));
        }
        System.out.println(sb);

    }

    // 방향을 어떤 쪽으로 바꾸는게 좋은지에 결정해주는 메서드
    static String directionChange(Point p){
        int leftTurn = 0; // 왼쪽으로 몇번 돌면 방향을 맞출 수 있는지
        int rightTurn = 0; // 오른쪽으로 몇번 돌면 방향을 맞출 수 있는지
        Point checkPoint = new Point(p.x, p.y, p.direction); // checkPoint를 복사해서 사용

        if(inside(checkPoint.x + dx[checkPoint.direction], checkPoint.y + dy[checkPoint.direction])){ // 회전할 필요가 없다면
            if(map[checkPoint.x + dx[checkPoint.direction]][checkPoint.y + dy[checkPoint.direction]] == '#'){
                return ""; // 공백을 리턴
            }
        }

        // 반시계 방향 체크
        for(int i = 0; i < 3; i++){ // 원래 방향을 바라볼 필요는 없으므로 최대 3번 반복
            leftTurn++;
            checkPoint.direction = (checkPoint.direction + 1) % 4; // 인덱스 맞춰주기
            if(inside(checkPoint.x + dx[checkPoint.direction], checkPoint.y + dy[checkPoint.direction])){
                if(map[checkPoint.x + dx[checkPoint.direction]][checkPoint.y + dy[checkPoint.direction]] == '#'){ // # 을 찾을 때까지 반복
                    break;
                }
            }
        }

        // 초기화 X -> 대신 방향을 원래 `p`에 적용하기 위해 저장해 둠
        int leftFinalDirection = checkPoint.direction;

        // 시계 방향 체크
        checkPoint.direction = p.direction; // 다시 원래 방향으로 복구
        for(int i = 0; i < 3; i++){
            rightTurn++;
            checkPoint.direction = (checkPoint.direction + 3) % 4; // 인덱스 맞춰주기
            if(inside(checkPoint.x + dx[checkPoint.direction], checkPoint.y + dy[checkPoint.direction])){
                if(map[checkPoint.x + dx[checkPoint.direction]][checkPoint.y + dy[checkPoint.direction]] == '#'){
                    break;
                }
            }
        }

        int rightFinalDirection = checkPoint.direction;

        // 방향 결정
        if(leftTurn > rightTurn){
            p.direction = rightFinalDirection; // 최종 방향을 원래 `p`에 적용
            return "R".repeat(rightTurn); // R을 몇번 사용했는지
        } else {
            p.direction = leftFinalDirection; // 최종 방향을 원래 `p`에 적용
            return "L".repeat(leftTurn); // L을 몇번 사용했는지
        }
    }


    // 경로는 백트래킹으로 만들 것임
    static void dfs(Point p){
        map[p.x][p.y] = '.'; // 최초 방문 체크
        if(p.x == endPoint.x && p.y == endPoint.y){
            map[p.x][p.y] = '#'; // 끝에 도달했다면 방문 체크 해제
            return;
        }
        String directionChange = directionChange(p); // 방향 맞춰주기
        for(char c : directionChange.toCharArray()){
            order.add(c); // 회전한만큼 List에 추가
        }
        order.add('A'); // 방향은 맞추어져 있으므로 A로 이동
        map[p.x + dx[p.direction]][p.y + dy[p.direction]] = '.'; // 한 칸 이동한 부분에 대해서만 방문 체크(도착한 칸은 dfs시작 시 체크 됨)
        dfs(new Point(p.x + 2 * dx[p.direction], p.y + 2 * dy[p.direction], p.direction)); // dfs
        map[p.x + dx[p.direction]][p.y + dy[p.direction]] = '#'; // 백트래킹
        map[p.x][p.y] = '#'; // 백트래킹
    }
    static boolean inside(int x, int y){
        if(x >= 0 && x < h && y >= 0 && y < w){
            return true;
        }
        return false;
    }



    static class Point {
        int x, y, direction;

        public Point(int x, int y, int direction) {
            this.x = x;
            this.y = y;
            this.direction = direction;
        }
    }
}

문제 풀이 전략

 

사실 어떻게 보면 단순한 dfs 문제이다. 다만, 정해진 이동 경로를 따라야 하기에 방향 전환을 어떻게 효율적으로 해주냐가 중요했다.

 

시작점과 끝점을 정한 뒤 2개의 경로를 갖게 된다.

방향 전환은 현재 방향을 기준으로 어떤 쪽으로 도는 게 효율적인지 판단한 후 추가한다.

또한, 방향 전환 메서드에 방향 전환 출력을 전적으로 일임하고 dfs메서드의 경우 정해진 방향으로 2칸 이동하는 것에만 초점을 두었다.