코딩테스트/프로그래머스

[Java] 프로그래머스 Level 1: 공원 산책

이덩우 2023. 5. 26. 10:48

- 문제설명

- 제한사항

- 예시 & 입출력 예

- 해결과정

  • 문제가 단순하지 않아 구현해야 하는 기능이 뭔지 천천히 생각해봤다.
  • 우선 첫번째로, 결국 출발점 S의 위치가 장애물을 만나는 상황이나 배열범위 밖으로 넘어가는 것을 체크하려면 먼저 기존 ["SOO", "OOO", "OOO"] 형식으로 주어져있는 1차원 park배열을 2차원배열로 만들어야 겠다는 생각을 했다. 
    이렇게 되면 추후 모든 상황을 비교할 때, 단순히 배열의 좌표값 끼리 비교하면 되므로 편리하다. 
  • 다음으로 출발지점 S의 좌표값을 얻는 로직을 작성하자. (S는 오직 하나 --> [1,2] 처럼 1차원 배열로 생성)
  • 다음으로 장애물 지점 X의 좌표값을 얻는 로직을 작성하자. 여기서 S와 동일한 논리로 구할 수 있지만, S는 무조건 하나인 반면, 장애물은 없을 수 도 있고 여러개가 있을 수 있기 때문에 우선 X의 개수를 구한 뒤 2차원 배열을 생성해 배열의 깊이를 장애물의 개수로 받도록 구성했다.
  • 이후, [E 2]라는 명령을 받았을 때, 우선 한발자국을 움직일 수 있냐를 판단해 boolean을 반환하는 메소드를 작성한 후 주어진 이동횟수를 모두 움직여도 true를 반환한다면 이동시켜라. 라는 메소드를 구분지어서 만들려고 시도했다.
    하지만 이렇게 구현하면 메인 메소드에서 for문을 여러번 사용해 결과값을 내보내야하므로, 최대한 메인 메소드를 간결하게 만들고자,  [E 2] 라는 명령이 들어왔을 때 한발자국씩 움직일 수 있는지를 먼저 판단한 후, 매개변수를 이용해 움직인 값을 저장하고 이후 갱신된 좌표값에서 다시 남은 발자국 수를 끝까지 이동시겼을 때도 문제가 없다면 저장된 매개변수 값을 기존의 S 좌표값으로 넣어준 뒤 반환시키는 메소드를 작성했다.
  • 이렇게 되면 구현에 필요한 모든 기능 각각 독립적인 메소드로 작성 됐기 때문에, 메인 메소드에서 각 메소드를 불러오기만 하면 된다.

- 솔루션

import java.util.*;

class Solution {     
    
    // 1. park 2차원 배열로 만들기 
    public char[][] makePark2D (String[] park, String[] routes) {
        char[][] park2D = new char[park.length][park[0].length()];
        for(int i = 0; i < park.length; i++) {
            for(int j = 0; j < park[0].length(); j++) {
                park2D[i][j] = park[i].charAt(j);
            }
        }
        return park2D;
    }
    // 2. 출발점 위치(S좌표) 찾기
    public int[] search_S (char[][] park2D) {
        for(int i = 0; i < park2D.length; i++) {
            for(int j = 0; j < park2D[0].length; j++) {
                if(park2D[i][j] == 'S') {
                    int[] s = {i,j};
                    return s;
                }
            }
        }
        return null;
    }
    // 3. 장애물 위치(X좌표들의 모음) 찾기
    public int[][] search_X (char[][] park2D) {
        int count = 0;
        int[][] x_null = {{-1, -1}, {-2,-2}};
        for(int i = 0; i < park2D.length; i++) {
            for(int j = 0; j < park2D[0].length; j++) {
                if(park2D[i][j] == 'X') {
                    count++;
                }
            }
        }
        if(count == 0) return x_null;    //장애물이 없으면, 이동에 문제가 생김 --> 상관없는 초기값 설정
            
        int [][] x_arr = new int[count][2];
        int index = 0;
        for(int i = 0; i < park2D.length; i++) {
            for(int j = 0; j < park2D[0].length; j++) {
                if(park2D[i][j] == 'X') {
                    int[] x = {i,j};
                    x_arr[index] = x;
                    index++;
                }
            }
        }
        return x_arr;
    }    
   
    // 4. [E 2]처럼 이동 할 때 장애물에 안걸리고 끝에 안걸리면 이동시켜라
    public int[] GoGo (char[][] park2D, int[][] x_arr, int[] s, String[] routes, int r_num) {
        int tmpX = s[1];
        int tmpY = s[0]; // 장애물 마주치는지, 배열을 나가버리는지 확인하는 변수
        int [] s_result = new int[2];
        
        for(int i = 0; i < x_arr.length; i++) {
            tmpX = s[1]; 
            tmpY = s[0]; //초기화 안해주면 numMoves가 x_arr.length만큼 곱해짐
            int numMoves = Integer.parseInt(String.valueOf(routes[r_num].charAt(2))); //몇칸 움직여야돼? 의 변수
            for(int j = 0; j < numMoves; j++){    
                    switch (routes[r_num].charAt(0)) {
                        case 'E' :
                            if((tmpY == x_arr[i][0] && tmpX+1 == x_arr[i][1]) || tmpX+1 == park2D[0].length){ 
                                s_result = s;
                                return s_result; // 메인에서 결과를 다시 여기로 가져오기 위해 사용
                            }
                            else {
                                tmpX += 1;
                            }
                            break;
                        case 'W' :
                            if((tmpY == x_arr[i][0] && tmpX-1 == x_arr[i][1]) || tmpX-1 == -1){ 
                                s_result = s;
                                return s_result;
                            }
                            else {
                                tmpX -= 1;
                            }
                            break;
                        case 'S' :
                            if((tmpY+1 == x_arr[i][0] && tmpX == x_arr[i][1]) || tmpY+1 == park2D.length){ 
                                s_result = s;
                                return s_result;
                            }
                            else {
                                tmpY += 1;
                            }
                            break;
                        case 'N' :
                            if((tmpY-1 == x_arr[i][0] && tmpX == x_arr[i][1]) || tmpY-1 == -1){ 
                                s_result = s;
                                return s_result;
                            }
                            else {
                                tmpY -= 1;
                            }
                            break;
            
                        default : break;
                    }               
            }
        }
        s[0] = tmpY;
        s[1] = tmpX;
        return s;
    }       
    // 메인 메소드
    public int[] solution(String[] park, String[] routes) {
        char[][] park2D = makePark2D(park, routes);       // 기존 1차원 park --> 2차원 배열로 생성
        int[] s = search_S(park2D);                       // 시작위치 찾기
        int[][] x_arr = search_X(park2D);                 // 장애물 어디있는지 찾기, 여러개일 수 있으므로 2차원배열로 생성

        int[] s_result = {};                              // 결과 받을 배열 생성
        for(int i = 0; i < routes.length; i++) {   
            s_result = GoGo(park2D, x_arr, s, routes, i); // 반복문을 통해 움직이라는 명령 수만큼 GoGo 메소드 실행
        }
        return s_result;
    }
}

 

- 배운점

  • 최근 자바의 객체지향성에 대해 공부를 해보며 코딩테스트를 준비할 때에도 객체지향성을 띈 코드를 작성하고자 했다.
  • 물론 코딩테스트 안에서 .java 파일을 여러개 만들어가며 클래스를 선언하는 방식으로는 사용할 수 없었지만, 
    기존처럼 단순히 메인 메소드 안에 모든 내용을 담아 구현하는것이 아닌 각 기능의 역할을 맡는 메소드를 작성했다.
  • 하나의 메인메소드 안에 모든 내용을 담았다면 분명 각 기능들이 강하게 종속되어 많은 오류가 발생했을 것이다.
    그러나, 각 메소드들을 독립적으로 실행시켜  메소드 간 논리 충돌을 방지하고 보다 가독성 있는 코드를 작성할 수 있었다.