안녕하세요. 회사와 함께 성장하고 싶은 KOSE입니다.

 

이번 포스팅은 프로그래머스 - 양궁대회 자바 풀이를 작성하고자 합니다.

 

문제 출처:  https://school.programmers.co.kr/learn/courses/30/lessons/92342

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

 

 

1. 풀이 소스

 

class Solution {

    int[] lion; // 라이언 점수
    int[] answer; // 정답을 복사할 배열
    int max = -1; // 최대값

    public int[] solution(int n, int[] info) {
        lion = new int[11];
        answer = new int[11];

        dfs(0, n, info);

        if (max == -1) return new int[]{-1}; // 경우의 수가 없는 경우 리턴

        return answer;
    }

    private void dfs(int depth, int n, int[] apeach) {
        if (depth == n) {
            int score = calculate(apeach); 
            if (max <= score) { // 참고 #1
                max = score; // 최대값 업데이트
                answer = lion.clone(); // lion의 배열의 값을 복사, 참조로 할 경우 lion이 바뀌면서 값이 바뀜
            }
            return;
        }

        // lion[i] <= apeach[i] 조건 (apeach의 10점을 3번 맞췄다면, lion은 10점을 얻으려면 4번을 맞춰야함)
        for (int i = 0; i < apeach.length && lion[i] <= apeach[i]; i++) {
            lion[i] += 1; // 해당 인덱스에 1 증가 -> 다음 dfs에서도 1 증가할 수 있음 따라서 위의 조건까지 반복
            dfs(depth + 1, n, apeach); 
            lion[i] -= 1; // 위에서 추가한 lion 배열에서 1을 감소하는 작업을 모든 dfs에서 수행하므로 원점 복구 가능
        }
    }

    int calculate(int[] apeach) {
        int apeachScore = 0;
        int lionScore = 0;

        for (int i = 0; i < lion.length; i++) {
            if (apeach[i] == 0 && lion[i] == 0) continue;
            if (apeach[i] >= lion[i]) apeachScore += (10 - i); 
            else lionScore += (10 - i);
        }

        if (apeachScore <= lionScore) return lionScore - apeachScore;
        return -1;
    }
    
    /*
    # 참고1
    해당 로직에서 스코어가 같으면 가장 작은 점수를 더 많이 맞춘 배열을 체크하지 않고 업데이트 하는 이유
    하단에 for문을 보면, i++로 상향식 반복문이 진행
    따라서, 가장 최근에 업데이트 되는 score는 가장 적은 점수의 과녁을 더 많이 맞춘 배열
    */
}

 

 

 

2. 문제 풀이 과정

 

해당 문제는 dfs로 해결해야 하는 문제입니다. 처음 풀이하는 과정에, 더 큰 점수 차이를 기록한 배열들이 여러 개인 경우 어떻게 차이를 구분해야 하는가에 대한 고민을 하였습니다.

 

이 과정에서, 주석 처리한 참고1을 확인하면 해당 문제를 해결할 수 있습니다.

상향식으로 반복문을 돌다보면, i++로 인해 배열의 끝에 있는 i를 업데이트하게 됩니다.

만약 더 큰 점수차이가 여러 개이더라도 더 적은 과녁을 하나라도 더 맞힌 배열을 리턴하게 됩니다. 

 

lion[i] += 1;

dfs()

lion[i] -= 1;

 

이 로직은, i 번째 과녁을 선택하지 않을 수 있습니다. 이 경우, 다음 인덱스로 넘겨주기 위해 재귀 성질을 이용하여 더한 만큼 다시 빼주면 원점으로 돌릴 수 있습니다.

 

자세한 풀이 과정은 마찬가지로 소스에 주석 처리하였습니다.

이상으로 프로그래머스 양궁대회 풀이를 마치겠습니다. 감사합니다.

안녕하세요. 회사와 함께 성장하고 싶은 KOSE입니다.

 

이번 포스팅은 프로그래머스 파괴되지 않은 건물 자바 풀이를 작성하고자 합니다.

 

문제 출처:  https://school.programmers.co.kr/learn/courses/30/lessons/92344#

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

 

 

1. 풀이 소스

 

class Solution {
    public int solution(int[][] board, int[][] skills) {
        int n = board.length; 
        int m = board[0].length;

        int[][] sum = new int[n + 1][m + 1];
        for (int[] skill : skills) {
            int r1 = skill[1], c1 = skill[2];
            int r2 = skill[3], c2 = skill[4];

            int degree = skill[5] * (skill[0] == 1 ? -1 : 1); // type == 1 이라면 degree 음수, type == 2라면 degree 양수

            sum[r1][c1] += degree;
            sum[r1][c2 + 1] += (degree * -1); // 주어진 인덱스보다 열 인덱스가 1 크면 부호 반대
            sum[r2 + 1][c1] += (degree * -1); // 주어진 인덱스보다 행 인덱스가 1 크면 부호 반대
            sum[r2 + 1][c2 + 1] += degree; // 주어진 인덱스보다 행, 열 인덱스가 각 1씩 크면, 부호 그대로
        }

        for (int r = 1; r < n; r++) {
            for (int c = 0; c < m; c++) { // 상하로 계산 주석 #50 (Ry = Ry-1 + Ry)
                sum[r][c] += sum[r - 1][c];
            }
        }

        for (int c = 1; c < m; c++) {
            for (int r = 0; r < n; r++) { // 좌우로 계산 주석 #54 (Cx = Cx-1 + Cx)
                sum[r][c] += sum[r][c - 1];
            }
        }

        int answer = 0;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                if (board[i][j] + sum[i][j] > 0) answer++;
            }
        }
        return answer;
    }
}

// 누적합
// 행렬 방식으로 아래 방향으로 행 인덱스 증가, 오른쪽 방향으로 열 인덱스 증가

// 만약 (0, 0)을 sum하면 sum 배열은 아래와 같음
// 4 -4
// -4 4

// Ry = Ry-1 + Ry (단, y는 1 이상, R은 행, y = 0이라면 Ry = Ry) 으로 업데이트
// 4 -4
// 0  0

// Cx = Cx-1 + Cx (단, x는 1 이상, C는 컬럼, x = 0이라면 Cx = Cx) 으로 업데이트
// 4 0
// 0 0

 

 

2. 문제 접근 과정: 

 

해당 문제는 for문으로 범위 내 모든 탐색을 하게 된다면, skill의 행 길이 25만 개 중 모든 skill이 (0,0) ~ (1000, 1000) 범위를 업데이트를 하게 된다면 25만 * 1000 * 1000 = 250,000,000,000 번 계산을 하게 됩니다. 즉 최대 K * N * M 번을 계산해야 합니다.

 

이를 해결하기 위한 개념이 누적합 개념입니다. 처음에 이를 활용하지 못하여, 다른 분의 풀이를 보고 문제를 해결하였습니다.

해결과정은 다음과 같습니다.

 

skill의 하나의 행은 [type, r1, c1, r2, c2, degree]로 되어 있습니다. 

이때, r1 ~ r2, c1 ~ c2 범위로 바로 degree를 업데이트하지 않고 각 꼭짓점을 누적합을 위한 범위로 설정합니다.

 

주석과 마찬가지로 하나의 예를 들면 다음과 같습니다.

board : [[1, 1], [1, 1], [1, 1]]

skill : [[2, 0, 0, 0, 0, 4], [1, 0, 0, 1, 0, 2]]

 

board의 누적합을 위해 int [][] sum이라는 2차원 배열을 생성합니다.

누적합은 특정 x에 대한 범위 계산을 위해 하나의 열과 하나의 행이 더 필요합니다. 따라서 sum의 크기는

board.length + 1, board[0].length + 1입니다.

 

누적합에 입력은 x1, y1 이 시작점, x2, y2가 끝 점이라고 하면

 

(x1, y1) += n

(x1, y2 + 1) += -n  

(x2 + 1, y1) += -n

(x2 + 1, y2 + 1) += n입니다.  

 

먼저 skill의 0번째 인덱스에 대한 결과를 보면 (0, 0), (0, 0) 범위에 4를 더해야 합니다. 

 

 

이어서 skill의 1번째 인덱스에 대한 누적합을 합니다. 

 

(0, 0) , (1, 0)에 대한 -2의 누적합을 위해 아래처럼 계산을 합니다.

(0, 0) += -2

(0, 1) += -(-2)

(2, 0) += -(-2)

(2, 1) += -2

 

 

 

이제 누적합에 대한 상하, 좌우 공식으로 누적합에 대한 결과를 계산합니다.

Ry = Ry-1 + Ry(y는 1 이상, R은 행, y = 0 일 때, Ry = Ry)

Cx = Cx-1 + Cx(x는 1 이상, C는 열, x = 0 일 때, Cx = Cx)

 

 

상하 계산

 

 

 

좌우 계산

 

 

이 결과를 기존에 board 이차원 배열 각 원소에 대응하는 값을 더해주면 값을 얻을 수 있습니다.

 

 

누적 합에 대한 개념을 이해하는데 많이 헷갈렸습니다.

실제 로그를 찍어보고 그려보니 어떠한 방식으로 적용되는지 이해할 수 있었습니다.

이상으로 파괴되지 않은 건물 풀이를 마치도록 하겠습니다.

 

감사합니다.!

안녕하세요. 회사와 함께 성장하고 싶은 KOSE입니다.

 

이번 포스팅은 프로그래머스 표 편집을 자바로 풀이한 과정을 정리하고자 합니다.

 

문제 출처: https://school.programmers.co.kr/learn/courses/30/lessons/81303#

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

 

1. 풀이 소스

 

import java.util.*;

class Solution {
    public String solution(int n, int k, String[] cmd) {
        Graph graph = new Graph(n, k);
        graph.run(cmd);
        return graph.getStatus();
    }
}

class Graph {

    Point point; // 현재 위치한 포인트를 기록
    Node start; // 시작 노드 (삭제하거나 다시 복구하는 과정에서 살아있는 맨 앞의 노드)
    Node last; // 마지막 노드 (삭제하거나 다시 복구하는 과정에서 살아있는 맨 뒤의 노드)
    Node[] nodes; // 노드를 설정할 입력 배열
    Stack<Node> dels = new Stack<>(); // 후입선출 복구를 위한 스택

    class Node {
        boolean isDel; // 출력을 위해 삭제 여부 기록
        Node pre; // 이전 노드
        Node next; // 마지막 노드
        Node () {}
    }

    class Point {
        Node p; // 현재 포인트의 노드
        Point(Node p) {
            this.p = p;
        }
    }

    Graph(int n, int k) {
        nodes = new Node[n];

        for (int i = 0; i < n; i++) nodes[i] = new Node();
        nodes[0].next = nodes[1];
        for (int i = 1; i < n - 1; i++) {
            nodes[i].pre = nodes[i - 1]; // nodes 배열에 i 인덱스에 있는 노드에 노드 연결
            nodes[i].next = nodes[i + 1];
        }
        
        nodes[n - 1].pre = nodes[n - 2];
        point = new Point(nodes[k]);
        start = nodes[0]; // start 초기화
        last = nodes[n - 1]; // last 초기화
    }

    void up(int dx) {
        Node node = point.p; // 현재 위치한 노드
        Node pre = node;
        int p = 0;
        while(p < dx) {
            pre = pre.pre; // up 은 행을 올려야 하므로 이전 노드로 탐색
            p++; 
        }

        point.p = pre;
    }

    void down(int dx) {
        Node node = point.p;
        Node next = node;
        int p = 0;
        while(p < dx) {
            next = next.next; // down은 행을 내려야 하므로 다음 노드로 탐색
            p++;
        }

        point.p = next;
    }

    // 위치한 노드에서 제거
    void del() {
        Node node = point.p;

        if (node == start) { // 만약 맨 앞의 노드가 삭제될 경우 해당 노드의 다음를 start로 설정
            start = node.next; 
            start.pre = null;
            point.p = start;
        } else if (node == last) {
            last = node.pre;
            last.next = null;
            point.p = last; // 마지막 인덱스의 경우 앞으로 이동
        } else {
            node.next.pre = node.pre; // 삭제되는 노드의 이전 노드와 다음 노드 연결
            node.pre.next = node.next;
            point.p = node.next; // 삭제되는 노드의 다음 노드로 포인트 변경
        }

        node.isDel = true; // 삭제 여부 표시
        dels.push(node); // 스택에 넣기
    }

    void restore() {
        Node node = dels.pop(); // 삭제된 노드 후입선출
        node.isDel = false; // 복구 설정

        if (node.pre != null && node.next != null) { // 중간에 위치한 노드인경우
            Node pre = node.pre; 
            Node next = node.next;
            pre.next = node; // 이전 노드의 다음 노드를 node로 복구
            next.pre = node; // 다음 노드의 이전 노드를 node로 복구
        } else if (node.pre != null) { // next == null
            Node pre = node.pre; 
            pre.next = node;
            last = node; // 마지막 노드 업데이트
        } else if (node.next != null) {
            Node next = node.next;
            next.pre = node;
            start = node; // 첫번째 노드 업데이트
        }
    }

    void run(String[] cmds) {
        for (String cmd : cmds) {
            char command = cmd.charAt(0);
            if (cmd.length() > 1) {
                int num = Integer.parseInt(cmd.split(" ")[1]);
                if (command == 'D') down(num);
                else up(num);
            }

            else {
                if (command == 'C') del();
                else restore();
            }
        }

    }

    String getStatus() {
        StringBuilder sb = new StringBuilder();
        for (Node node : nodes) {
            if (node.isDel) sb.append("X");
            else sb.append("O");
        }

        return sb.toString();
    }
}

 

 

 

2. 풀이 과정

 

해당 문제를 읽고 LinkedList를 사용하거나 ArrayList를 사용할 경우 삭제한 노드를 복구하는 과정에서 시간초과가 발생할 것 같다는 생각을 하였습니다. 따라서, LinkedList를 직접 구현하여, 만약 삭제 후 복구를 해야 한다면, 스택에서 노드를 가져온 후,
연결관계를 기억한 노드에 다시 연결관계를 설정해주는 것이 좋을 것 같다는 생각을 하였습니다. 따라서, 노드를 저장할 배열을 설정한 후, 연결관계를 설정하고 pre와 next로 인스턴스를 설정하여 문제를 해결하였습니다.

 

삭제 및 복구하는 과정에 대한 자세한 내용을 정리하면 다음과 같습니다.

  • 만약 OOOOO 상황에서 포인트가 인덱스 2에 위치한 경우, OOXOO가 설정되게 됩니다.
  • 이 과정에서 2번 인덱스의 이전, 다음 노드인 1번 3번 노드는 서로 연결 시켜 주어야 합니다.
  • 따라서, 1번 노드.next = 3번 노드, 3번 노드. pre = 1번 노드로 설정할 수 있습니다.
  • 복구하는 과정에서는 '후입선출'이므로, 2번 노드가 삭제되는 시점에 이전, 이후 노드는
    반드시 2번 노드가 다시 복구된다면 두 노드도 살아있게 됩니다.

    삭제: OOXOO -> OXXOO -> OXXXO 상태
    복구: OXXOO -> OOXOO (후입선출)

 

따라서, 삭제되는 시점에는 삭제되는 노드의 연관관계를 바꾸지 않는다면 다시 복구하는 과정을 설정할 수 있습니다.

기타 다른 부분들은 1번 풀이에 자세한 주석으로 작성하였습니다.

 

 

 

3. 더 좋은 코드 공유

 

 

테스트는 통과하였지만, 최대 약 325ms가 걸렸습니다.

다른 블로그에 더 좋은 풀이로 작성하신 코드가 있어 공유드립니다.

 

https://moonsbeen.tistory.com/294

 

[프로그래머스]표 편집 - JAVA

** 풀이가 추가되었습니다. 추가된 풀이가 정확한 풀이입니다. ** [프로그래머스]표 편집 코딩테스트 연습 - 표 편집 8 2 ["D 2","C","U 3","C","D 4","C","U 2","Z","Z"] "OOOOXOOO" 8 2 ["D 2","C","U 3","C","D 4","C","U 2","

moonsbeen.tistory.com

 

해당 블로거분의 코드를 실행하면 효율성 테스트 기준 무려 100ms 평균으로 통과할 수 있는 코드입니다.

 

이상으로 해당 문제에 대한 리뷰를 마치도록 하였습니다. 감사합니다.!

안녕하세요. 회사와 함께 성장하고 싶은 KOSE입니다.

 

이번 포스팅은 프로그래머스 양과 늑대에 대한 자바 풀이를 작성하도록 하겠습니다.

 

문제 출처: https://school.programmers.co.kr/learn/courses/30/lessons/92343

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

 

1. 풀이 소스

 

import java.util.*;

class Solution {
    
    int maxSheepCnt; // 최대 양의 수
    int totalSheepCnt; // 총 양의 수
    ArrayList<Integer>[] nodes; // A 노드의 자식 노드(B, C, D)와 부모(R)를 포함한 조상 노드(X, Y)의 자식 노드 중 방문해야하는 노드들
    
    public int solution(int[] info, int[][] edges) {
        
        nodes = new ArrayList[info.length]; 
        
        for (int i = 0; i < nodes.length; i++) nodes[i] = new ArrayList<>();
        
        for (int i = 0; i < info.length; i++) {
            if (info[i] == 0) totalSheepCnt++; // 총 양의 수를 구하여, 만약 최대 양의 수가 총 양의 수와 같다면 무조건 리턴
        }
        
        if (totalSheepCnt == 0) return 0; // 만약 총 양의 수가 0 이라면 바로 종료
        
        for (int[] edge : edges) {
            int parent = edge[0]; 
            int child = edge[1];
            nodes[parent].add(child); // 단방향 간선을 설정 (부모 -> 자식으로만 이동)
        }
     
        dfs(0, 0, 0, new ArrayList<>(), info);
        
        return maxSheepCnt;
    }
    
    void dfs(int sheepCnt, int wolfCnt, int node, List others, int[] info) {
        if (maxSheepCnt == totalSheepCnt) return; // 총 양의 수와 최대 방문한 양의 개수가 같다면 dfs 종료
        
        if (info[node] == 0) sheepCnt++;
        else wolfCnt++;
        maxSheepCnt = Math.max(maxSheepCnt, sheepCnt);
        
        if (sheepCnt <= wolfCnt) return; // 문제에서 늑대가 양의 개수보다 더 크다면 현재 dfs 종료
        
        List<Integer> remains = new ArrayList<>();
        remains.addAll(others); // 재귀로 남아 있는 리스트를 그대로 새로운 리스트에 추가
        // 예제 1 기준 루트 노드부터 선택되지 않았던 자식노드 ex)8, 현재 노드가 4번이라면 방문해야하는 노드2 추가
        
        if (!nodes[node].isEmpty()) {
            remains.addAll(nodes[node]); // 현재 노드의 자식 노드 추가
        }
        
        remains.remove(Integer.valueOf(node)); // remove에 레퍼타입을 적용하면 해당 값은 index가 아닌 value로 제거 가능
        
        for (int nextNode : remains) {
            dfs(sheepCnt, wolfCnt, nextNode, remains, info);
        }
    }
}

 

 

2. 주의할 점

 

 

  • 일반적인 dfs를 적용하게 된다면 다음과 같이 자식 노드를 기준으로 탐색하게 됩니다. 하지만, 늑대의 개수가 양보다 큰 경우 8번을 먼저 방문할 수 없습니다. 즉, 루트 노드(0)의 자식 노드 8번을 거치지 않게 됩니다. 따라서, 이 문제를 극복하기 위해 특정 A 노드로 방문하고자 할 때, 부모노드와 조상 노드에서 방문하지 않은 노드를  아래의 사진처럼 추가하여 재방문할 수 있도록 해야 합니다.

 

  • 문제 되는 부분은 방문하지 않은 노드를 선택할 때, 부모 노드의 자식 노드를 전부 추가하게 되면 양 2가 중복되게 됩니다. 
  • 자바의 list.remove()를 활용할 때 레퍼 변수 Integer 변수를 사용하게 되면 인덱스가 아닌 값을 제거할 수 있습니다.

 

나머지는 주석으로 처리하였습니다! 이상으로 해당 문제 리뷰를 마치겠습니다.

 

감사합니다.!!!

안녕하세요. 회사와 함께 성장하고 싶은 KOSE입니다.

 

이번 포스팅은 프로그래머스 등산코스 정하기 문제를 해결한 과정을 정리하도록 하겠습니다.

 

문제 출처: https://school.programmers.co.kr/learn/courses/30/lessons/118669#

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

 

1. 문제 해결 방안

 

해당 문제는 n번 노드로 도착하는 과정에서 하나의 특정 경로가 거친 가중치들 중 가장 큰 가중치를 intensity로 설정합니다.
만약 n번 노드로 도착할 수 있는 경로가 여러 개 일 때, 특정 경로들의 intensity를 비교하여 가장 작은 intensity를 선정합니다.

 

즉 to 노드는 from 노드의 intensity와 from과 to 노드 사이의 간선의 가중치 중 더 큰 값을 distance로 설정한 후,
distance = max(intensity[from.노드 번호], edge(from.노드번호, to.노드 번호))

 

to 노드의 노드 번호에 있는 intensity의 값보다 distance가 더 작다면 intensity를 distance로 업데이트해야 합니다.

if intensity[to.노드 번호] > distance: intensity[to.노드 번호] = distance

 

시작은 gates 배열에서 gate를 큐에넣는 방식으로 다익스트라 알고리즘을 활용할 수 있습니다.

 

 

 

2. 소스

 

import java.util.*;

class Solution {
    public int[] solution(int n, int[][] paths, int[] gates, int[] summits) {
        Graph graph = new Graph(n);
        graph.setGate(gates);
        graph.setSummit(summits);
        
        for (int[] path : paths) {
            int s = path[0];
            int d = path[1];
            int w = path[2];
            
            graph.addEdge(s, d, w);
        }

        return graph.dijkstra(n, gates, summits);
    }
}


class Graph {
    
    Set<Integer> gateSet = new HashSet<>();
    Set<Integer> summitSet = new HashSet<>();
    List<List<Node>> nodes = new ArrayList<>(); // nodes의 i번 인덱스에는 i번이 갈 수 있는 adjacent 
    
    class Node {
        int d; // 만약 새로 출발하는 노드 == queue에 넣어야 하는 노드(to) 라면 도착해야하는 노드의 주소, 이미 도착했다면 == queue에서 나온 노드(from) 도착한 현재 노드 번호
        int w; // 도착지 노드로 가는데 필요한 가중치, 만약 이 노드가 출발 노드라면 이 노드로 도착하는데 필요했던 가중치
        
        Node (int d, int w) {
            this.d = d;
            this.w = w;
        }
    }
    
    Graph(int n) {
        for (int i = 0; i <= n; i++) nodes.add(new ArrayList<>());
    };
    
    void setGate(int[] gates) {
        for (int gate : gates) {
            gateSet.add(gate);
        }
    }
    
    void setSummit(int[] summits) {
        for (int summit : summits) {
            summitSet.add(summit);
        }
    }
    
    void addEdge(int s, int d, int w) {        
        if (gateSet.contains(s) || summitSet.contains(d)) {
            nodes.get(s).add(new Node(d, w)); // 만약 s가 게이트라면 출발 노드만 존재 or d가 정상이라면 도착 노드만 존재
        } else if (gateSet.contains(d) || summitSet.contains(s)) {
            nodes.get(d).add(new Node(s, w)); 
        } else {
            nodes.get(d).add(new Node(s, w)); 
            nodes.get(s).add(new Node(d, w));   
        }
    }
    
    int[] dijkstra(int n, int[] gates, int[] summits) {
        
        Queue<Node> queue = new ArrayDeque<>();
        int[] intensity = new int[n + 1]; 
        
        Arrays.fill(intensity, Integer.MAX_VALUE);
        
        for (int gate : gates) {
            queue.add(new Node(gate, 0)); // gate로 도착하는 가중치는 0 (출발지이므로)
            intensity[gate] = 0;
        }
        
        while(!queue.isEmpty()) {
            Node from = queue.poll(); // 현재 도착한 노드는 출발 노드로 변경

            for (int i = 0; i < nodes.get(from.d).size(); i++) {
                Node to = nodes.get(from.d).get(i);
                
                int distance = Math.max(intensity[from.d], to.w);
                if (intensity[to.d] > distance) {
                    intensity[to.d] = distance;
                    queue.add(new Node(to.d, distance));
                }
            }            
        }
        
        int summit = Integer.MAX_VALUE; 
        int intense = Integer.MAX_VALUE;
        
        Arrays.sort(summits); // 만약 intense가 같다면 가장 작은 정상 번호를 구해야함
        
        for (int sm : summits) {
            if (intensity[sm] < intense) {
                summit = sm;
                intense = intensity[summit];
            }
        }
        
        return new int[]{summit, intense};
        
    }
    
}

 

 

3. 부분 해결 과정

 

while(!queue.isEmpty()) {
            Node from = queue.poll(); // 현재 도착한 노드는 출발 노드로 변경

            for (int i = 0; i < nodes.get(from.d).size(); i++) {
                Node to = nodes.get(from.d).get(i);
                
                int distance = Math.max(intensity[from.d], to.w);
                if (intensity[to.d] > distance) {
                    intensity[to.d] = distance;
                    queue.add(new Node(to.d, distance));
                }
            }            
        }

 

이 코드는 queue에서 나온 노드는 from, queue에 넣을 노드를 to로 설정하였습니다.

즉, 다음 목적지로 가야 할 노드에 설정된 w와 출발지까지 오는 과정에 설정된 노드의 intensity를 비교하여
더 큰 값을 distance로 설정합니다. (distance는 문제의 요구사항에 맞춰 현재까지 온 경로 중에 가장 큰 값으로 설정해야 함)

 

만약 다음 목적지로 가야할 곳의 intensity값 보다 새로 온 경로의 distance가 더 작다면 문제의 요구사항에 의해
더 작은 intensity를 설정해야 합니다.

 

이 코드의 진행 순서를 정리하면 다음과 같습니다.

 

  • from gate: 1, summit: 4
    edge => (1, 2, 3), (1, 3, 1)
    edge => (2, 4, 7), (3, 4, 1)
  • gate.d: 1, gate.w: 0 설정
  • while 루프 시작       

    nodes.get(1) -> [2, 3]

    1) if from d = 1, w = 0, 도착지 2로 갈 때,
    2) Node to => d = 2, w = 3
    3) distance = max(intensity[from.d], to.w)
    -----> intensity[from.d] => intensity[1] = 0, to.w = 3, 고로 distance = 3
    4) intensity[2] > distance 이므로, intensity[2] = 3
    5) queue (new Node(2, 3))
            
    6) if from d = 1, w = 0, 도착지 3으로 갈 때,
    7) Node to => d =  3, w = 1 
    8) 앞의 도착지 2로 갈 때와 같은 방법으로 distance 계산 -> distance = 1
    9) 앞의 intensity[] > distance 비교와 같은 방법으로 계산 -> intensity[3] = 1
    10) queue (3, 1)
            
    11) if from d = 2, w = 3, 도착지 4로 갈 때
    12) Node to = nodes.get(2) -> d =  4 w = 7
    13) intensity[2] =  3, to.w = 7 이므로 distance = 7
    14) intensity[4] = distance = 7
    15) queue (4, 7) ----> graph.get(4).size() == 0 (도착 노드이므로) --> 종료
            
    16) if from d = 3, w = 1, 도착지 4로 갈 때
    17) Node to = nodes.get(3) -> d = 4, w = 1
    18) intensity[3] = 1, to.w = 1로 distance = 1
    19) intensity[4] > distance (앞에서 7로 업데이트 되었으므로 ) 성립하므로  intensity[4] = distacne = 1
    20) queue (4, 1) ----> graph.get(4).size() == 0 ---> 종료

    따라서, summit 4, intensity[4] = 1가 됩니다.

 

이상으로 프로그래머스 등산코스 정하기 정리를 마치도록 하겠습니다.

안녕하세요. 회사와 함께 성장하고 싶은 KOSE입니다.

 

이번 포스팅은 프로그래머스 dp 문제 중 우박수열 정적분 문제를 해결한 과정을 정리하고자 합니다.

앞 서, 약수를 먼저 구하고 dp를 처리하는 '억억단을 외우자' 문제를 해결한 이후라 비교적 빠르게 해결할 수 있었습니다.

문제 링크는 다음과 같습니다. 

https://school.programmers.co.kr/learn/courses/30/lessons/134239

 

 

 

1. 문제 해결 소스 

 

import java.util.*;

class Solution {
    
    List<Integer> heights = new ArrayList<>();
    
    public double[] solution(int k, int[][] ranges) {
        double[] answer = new double[ranges.length]; // 정답 배열
        heights.add(k); // k를 단계별로 처리한 값을 heights에 넣기 위한 arrayList
        
        while (k > 1) {
            if (k % 2 == 0) k /= 2;
            else {
                k *= 3;
                k++;
            }
            heights.add(k);
        } 
        
        double[] dp = new double[heights.size()]; // dp 선언 

        // 너비 단위는 1 (ranges가 int 이차원 배열이므로), 인덱스 i의 k 와 i - 1의 k로 사다리꼴 넓이 구하기 
        for (int i = 1; i < heights.size(); i++) {
            dp[i] = dp[i - 1] + (heights.get(i - 1) + heights.get(i)) * 1.0 / 2; // 이전 dp를 더함으로써 누적합
        }
        
        int last = heights.size() - 1;
        for (int i = 0; i < ranges.length; i++) {
            int a = ranges[i][0]; // 범위 a 
            int b = ranges[i][1]; // 범위 b 
            
            if (a > heights.size() || (last + b) < a) {  // 정적분되는 실제 범위가 유효하지 않은 경우
                // ex) last : 6, last = -3, a = 4 ==> 양수 정적분 불가 
                answer[i] = -1.0;
                continue;
            }
            
            double total =  dp[last];
            answer[i] = total - dp[a] - (dp[last] - dp[last + b]); // 전체에서 양측 범위에 해당하는 적분 결과 빼서 중간 범위 구하기
        } 
   
        return answer;
    }
}

 

 

 

2. 주의할 점

  • 문제에서, 1-1, 1-2로 되어 있는 파트 단계별 순서가 아닌 분기되는 조건이므로 2로 나누었을 때, 홀수가 되더라도 1-1 이후, 1-2로 가는 것이 아니라 2번 스텝으로 가야 합니다.
  • 적분 구간이 다소 헷갈릴 수 있었습니다.
    range = [0, 0]이 전구간 적분 결과가 나오는 이유는, a에서 b까지 적분 되는 결과에서 
    range[0]이 의미하는 바는 x = 0에서 range[0]만큼 떨어진 구간이고, 
    range[1]은 x = lastIndex 에서 range[1]만큼 더한 (range[1]이 음수이므로) 범위를 의미합니다. 
    따라서, [0, 0]은 x = 0 - 0 = 0, x = last - 0 = last이므로 전 구간 적분입니다.
  • a 에서 b까지 이루어지는 적분에서 만약 a 가 b 보다 큰 경우는 -1.0을 리턴하도록 문제에서 정의되어 있으므로,
    이 부분을 반드시 체크해야합니다.!

 

이상으로 프로그래머스 우박수열 정적분 문제를 마치도록 하겠습니다.

감사합니다.!

 

안녕하세요. 회사와 함께 성장하고 싶은 KOSE입니다.

 

오늘은 제가 준비했던 회사의 코딩테스트를 진행한 날이었습니다.

정말 한 없이 실력이 모자다라는 것을 느끼고 십 분간 좌절한 후, 현실을 냉철하게 분석하는 시간이 되었습니다.

먼저, 간단하게 회고를 남기고 이어서 프로그래머스 미로 탈출 문제를 해결한 과정을 정리하도록 하겠습니다.

 

1. 회고

제가 본시험은 120분간 4문제를 해결해야 했습니다. 최초 목표는 60, 60분씩 두 문제를 해결하고자 하는 마음으로 출발했습니다.

하지만 1번 문제의 다소 복잡한 구현에서 105분을 소비해 버렸습니다. 예상하지 못한 테스트 케이스를 제가 임의로 추가한 후에, 처음 작성한 문제가 무엇이었는지 파악하고 수정하는데 많은 시간을 소모했습니다. 결국 1번에서 제공해 주신 테스트 케이스는 성공했지만, 2번 문제를 분석할 시간도 없이 2번을 흐적이다가 끝났습니다.

 

끝난 후, 문제의 난이도와 별개로 정말 제 실력이 많이 부족하다는 것을 느끼게 되었습니다. 하지만 큰 경험으로 현재 문제점을 파악하고 앞으로의 공부 방향을 어떻게 잡아야 하는지 느끼게 되었습니다. 알고리즘뿐만 아니라, 특정 문제가 주어졌을 때, 이를 구현하는 실력과 빠른 시간 내에 문제를 파악하고 코드를 작성해야하는 것을 느끼게 되었습니다.

 

이에 현실을 자각할 수 있었고, 헤이해진 몸과 정신을 다 잡을 수 있는 기회가 되었습니다. 좌절할 시간도 아깝기에 바로 다른 부분을 공부하며 흐름이 끊기지 않도록 하였습니다.

 

그럼 본론으로 들어가서 프로그래머스 미로 탈출 문제를 해결하도록 하겠습니다.

문제 링크: https://school.programmers.co.kr/learn/courses/30/lessons/159993

 

 

2. 프로그래머스 미로 탈출 해결하기

 

<소스>

 

// 4: 18 ~ 5: 27//

import java.util.*;

class Solution {
    
    static final int[] D_X = {-1, 0, 1, 0}; // x 이동 좌표
    static final int[] D_Y = {0, -1, 0, 1}; // y 이동 좌표
    
    int eX; // 도착지 x좌표
    int eY; // 도착지 y좌표
    
    public int solution(String[] maps) {        
        int sX = 0, sY = 0, lX = 0, lY = 0; // s는 출발지 좌표, l은 레버 좌표
        
        for (int i = 0; i < maps.length; i++) {
            for (int j = 0; j < maps[0].length(); j++) {
                if (maps[i].charAt(j) == 'S') {
                    sX = i;
                    sY = j;
                } else if (maps[i].charAt(j) == 'E') {
                    eX = i;
                    eY = j;
                } else if (maps[i].charAt(j) == 'L') {
                    lX = i;
                    lY = j;
                }
            }
        }
        
        boolean[][] visited = new boolean[maps.length][maps[0].length()]; // 방문여부
        
        int step = bfs(new Node(sX, sY, 0), lX, lY, maps, visited, false);  // bfs로 먼저 시작점 -> 레버의 최단 거리 
        if (step == -1) return -1; // 만약 갈 수 없는 경우
        
        int sToE = bfs(new Node(sX, sY, step * 2), eX, eY, maps, visited, true);  // 시작점 -> 레버 -> 시작점 -> 도착점   
        int lToE = bfs(new Node(lX, lY, step), eX, eY, maps, visited, true);   // 시작점 -> 레버 -> 도착점
        
        if (lToE == -1) return sToE;
        else if (sToE == -1) return lToE;
        return Math.min(lToE, sToE);
    }
    
    private int bfs(Node node, int eX, int eY, String[] maps, boolean[][] visited, boolean refresh) {
        Queue<Node> queue = new ArrayDeque<>(); // bfs를 위한 queue
        queue.add(node); 
        
        if (refresh) {
            for (int i = 0; i < visited.length; i++) {
                for (int j = 0; j < visited[i].length; j++)
                    visited[i][j] = false; // 방문 여부 초기화
            }
        }

        while(!queue.isEmpty()) {
            
            Node n = queue.poll(); // 큐에서 꺼내기
            
            if (n.x == eX && n.y == eY) return n.step; // 최단 거리 리턴
            if (visited[n.x][n.y]) continue; // 방문한 경우 패스
            
            visited[n.x][n.y] = true; // 방문 여부 설정
            
            for (int k = 0; k < 4; k++) {
                int newX = n.x + D_X[k]; // 새로운 좌표 설정
                int newY = n.y + D_Y[k];
                
                if (validRange(newX, newY, maps, visited)) {
                    queue.add(new Node(newX, newY, n.step + 1));
                }
            }
        }
        return -1;
    }
    
    // 방문한 경우가 아니고 유효한 인덱스 && 지나갈 수 있는 경로일 경우
    private boolean validRange(int x, int y, String[] maps, boolean[][] visited) {
        return x >= 0 && y >= 0 && x < maps.length && y < maps[x].length()
            && !visited[x][y] && (maps[x].charAt(y) != 'X');
    }
    
    class Node {
        int x;
        int y;
        int step;
        
        Node (int x, int y, int step) {
            this.x = x;
            this.y = y;
            this.step = step;
        }
    }
}

 

 

 

3. 특이점

 

이 문제를 처음에 "이때 아직 레버를 당기지 않았더라도 출구가 있는 칸을 지나갈 수 있습니다."라는 문구를 보고, 레버의 유무는 크게 상관이 없는 것인가 의아해하면서 일반적인 bfs로 문제를 해결했습니다.

 

하지만, 테스트 케이스를 실패하였고, 문제를 다시 읽어보고 레버의 문제 출제 의도를 파악해 보았습니다. 

여기서 말하는 레버를 당기지 않았더라도 출구가 있는 칸을 지나갈 수 있다는 의미는, 출구를 통과하여 해당 알고리즘을 끝내는 것이 아니라, 말 그대로 지나갈 수 만 있다는 의미였습니다.

즉, 시작점 S -> L -> E로 레버를 통과해야만 E로 가서 미로를 통과할 수 있습니다.

 

 

 

4. 가능한 경우와 알고리즘

 

미로를 탈출하거나 종료하기 위해서는 먼저 S -> L -> E의 루트를 거치거나 혹은 E로 갈 수 없는 경우로 종료를 해야 합니다.

 

여기서 확인할 수 있는 경우는 크게 4가지입니다.

 

  • S -> L -> E  (시작점에서 도착지로 가는 과정에 레버가 존재)  return step
  • S -> L -> S -> E  (레버와 도착지 사이에 출발지가 있는 경우) return step
  • S -> L -> || --x-----   E  레버까지는 도착했지만 레버에서 도착지로 갈 수 없는 경우  return -1
  • S ---x---  ||  ---x----   L  레버까지 갈 수 없는 경우 return -1

 

따라서, 

먼저 S -> L로 출발지에서 레버까지의 step을 bfs로 계산한 후, 만약 -1이라면 그대로 return으로 해당 메서드를 종료하고,
만약 -1이 아닌 양수가 나온다면 출발지에서 레버까지 갈 수 있는 최단 경로이므로 해당 step을 더하여 

L -> E (1)과 S -> E (2)를 계산합니다.

 

여기서 주의할 점은 S -> E가 되는 경우 S -> L까지 이동한 경로인 step * 2를 해야 다시 S에 도착할 수 있습니다.

그리하여 만약 (1) 과 (2) 중 하나라도 -1이라면 다른 하나가 정답이 되고 둘 다 -1인 경우 -1, 둘 다 -1이 아니라면 최솟값이 정답이 됩니다.

 

정리하면, bfs를 적용하되 가능한 경로 경우를 나눠서 최소 경로를 판단해야하는 문제였습니다.

테스트 케이스와 정답 제출 결과는 다음과 같습니다.

 

["SOOOL", "OXXXO", "OXXXO", "OXXXO", "EXOOO"] , 12

["SOOOL", "OXXXX", "OXXXX", "OXXXX", "EXXXX"], 12

["SOOOL", "XXXXO", "OXXXO", "OXXXO", "EXOOO"],  -1

 

 

이상으로 프로그래머스 미로 탈출 문제를 정리하도록 하겠습니다. 

감사합니다.!

안녕하세요. 회사와 함께 성장하고 싶은 KOSE입니다.

 

이번 포스팅은 BFS의 2206번 자바 풀이를 작성하고자 합니다.

BFS는 Queue를 활용하기 때문에 선입 선출로 처리되며 최단 거리를 찾는데 효율적입니다.

 

문제 링크: https://www.acmicpc.net/problem/2206

 

2206번: 벽 부수고 이동하기

N×M의 행렬로 표현되는 맵이 있다. 맵에서 0은 이동할 수 있는 곳을 나타내고, 1은 이동할 수 없는 벽이 있는 곳을 나타낸다. 당신은 (1, 1)에서 (N, M)의 위치까지 이동하려 하는데, 이때 최단 경로

www.acmicpc.net

 

 

1. 코드

 

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;

import java.util.ArrayDeque;
import java.util.Queue;

import static java.lang.Integer.parseInt;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String[] inputs = br.readLine().split(" ");

        int n = parseInt(inputs[0]);
        int m = parseInt(inputs[1]);

        WallMap wallMap = new WallMap(n, m);
        for (int i = 0; i < n; i++) {
            String str = br.readLine();
            for (int j = 0; j < m; j++) {
                wallMap.addMap(i, j, str.charAt(j));
            }
        }

        System.out.println(wallMap.findShortRoad());
    }
}

class WallMap {

    static final int[] D_X = {1, 0, -1, 0};
    static final int[] D_Y = {0, 1, 0, -1};

    char[][] map; // 입력한 벽을 저장할 2차원 배열
    boolean[][][] visited; // 방문 여부 3차원 배열

    class Road {
        int x; // x 좌표
        int y; // y 좌표
        int step; // bfs 단계
        boolean destroyed; // 길에 벽이 있다면 파괴 여부

        Road(int x, int y, int step, boolean destroyed) {
            this.x = x; 
            this.y = y;
            this.step = step;
            this.destroyed = destroyed;
        }

    }

    WallMap(int n, int m) {
        map = new char[n][m];
        visited = new boolean[n][m][2];
    }

    void addMap(int x, int y, char c) {
        map[x][y] = c;
    }
 
    int findShortRoad() {
        Queue<Road> queue = new ArrayDeque<>(); // 큐 생성
        queue.add(new Road(0, 0, 1, false)); // 첫 번째 길 등록 (x = 0, y = 0, step = 1, 파괴 X)

        // bfs
        while(!queue.isEmpty()) {
            Road road = queue.poll(); // 큐에서 저장한 Road 인스턴스 빼기

            if (road.x == map.length - 1 && road.y == map[0].length - 1) {
                return road.step; // 만약 찾아야 하는 마지막 배열에 위치한 경우 종료
            }

            for (int k = 0; k < D_X.length; k++) {
                int newX = road.x + D_X[k]; // dx dy 
                int newY = road.y + D_Y[k];

                if (validRange(newX, newY)) continue; // 인덱스 유효한 배열 확인

                if (map[newX][newY] == '0') { // 만약 map에 있는 값이 벽이 아니라면
                    if (!road.destroyed && !visited[newX][newY][0]) {
                        
                        // 큐에 new Road()로 새로운 길의 x, y, step + 1, 벽 파괴 X 입력하기
                        queue.add(new Road(newX, newY, road.step + 1, false));
                        visited[newX][newY][0] = true; // 방문 여부 등록
                    }

                    // 만약 길이 파괴된 상태, 방문하지 않은 경우 true를 queue에 넣기
                    else if (road.destroyed && !visited[newX][newY][1]) {
                        queue.add(new Road(newX, newY, road.step + 1, true));
                        visited[newX][newY][1] = true;
                    }
                }

                // 만약 새로운 길이 벽이라면, 파괴되지 않은 경우만 큐에 넣기
                else if (map[newX][newY] == '1') {
                    if (!road.destroyed) {
                        queue.add(new Road(newX, newY, road.step + 1, true));
                        visited[newX][newY][1] = true;
                    }
                }
            }
        }
        return -1;
    }

    boolean validRange(int x, int y) {
        return x < 0 || y < 0 || x >= map.length || y >= map[0].length;
    }


}

 

 

 

2. 풀이 중 어려웠던 점

 

이전 BFS 문제를 해결할 때는 인스턴스 배열을 생성해서 그 안에 인스턴스를 넣고 초기화하는 작업을 수행했습니다. 만약 입력 시점에 1이 입력된다면 possibleBreakWall 리스트에 추가하는 방법을 했습니다.

 

하지만, 시간초과가 발생했고 비효율적인 방법이었다는 점을 확인했습니다. 만약, Road 배열을 선언하고 그 안에 road 인스턴스를 넣는다면 벽을 깬 방법이 올바르지 않을 때, 매번 배열에 등록된 인스턴스를 전부 초기화하는 작업을 수행했어야 했습니다.

 

class WallMap {

    Road[][] roads;
    List<Road> possibleBreakWall = new ArrayList<>();

 

이 문제를 해결하기 위해,

따라서, char[][] map이라는 배열을 등록하여, Queue에 넣을 때마다 인스턴스에 상태를 기록하는 방법을 적용했습니다.

 

이 방법의 장점은

BFS는 계속 순회하되, map이라는 벽을 기록한 상태 정보는 바뀌지 않기 때문에
Queue에 등록되는 인스턴스의 벽을 깼는지 여부를 파악하고 다음 스텝에 새로운 Road 인스턴스를 생성하며 현재 스텝 + 1, 벽 깬 여부 true만 이어서 등록하면 됩니다.

 

즉, Queue에 있는 road는 해당 좌표와 현재 벽을 깼는지 그리고 방문한 상태인지만 파악한다면 

1 -> 2 -> 3(벽 깸)  ->  5  ->  7 (벽 못깸)

           -> 4          ->  6   ->  7(벽 깸)  -> 도착지

           -> 9(벽 깸)  -> 4 이미 2번 노드의 자식 노드인 4번 노드에서 먼저 방문 했으므로 종료

 

이런식으로 처리 될 수 있습니다.

 

이상입니다!! 감사합니다!!

 

 

안녕하세요. 회사와 함께 성장하고 싶은 KOSE입니다.

 

이번 포스팅은 BFS에서 비슷한 문제인 백준 2178, 1697번의 자바 풀이를 작성하고자 합니다.

BFS는 Queue를 활용하기 때문에 선입 선출로 처리되며 최단 거리를 찾는데 효율적입니다.

 

문제 링크: https://www.acmicpc.net/problem/2178

문제 링크: https://www.acmicpc.net/problem/1697

 

코드와 주석으로 먼저 제시한 후, 중요한 포인트를 작성하도록 하겠습니다.!

 

1. 코드 

 

<Boj2178>

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;

import java.util.Queue;
import java.util.ArrayDeque;
import java.util.StringTokenizer;

import static java.lang.Integer.parseInt;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());

        int n = getInt(st);
        int m = getInt(st);
        Maze maze = new Maze(n, m); // 미로 인스턴스 생성

        String str;
        for (int i = 0; i < n; i++) {
            str = br.readLine();
            for (int j = 0; j < m; j++) {
                maze.setInit(i, j, Character.getNumericValue(str.charAt(j))); // 미로의 1 혹은 0 입력
            }
        }

        maze.findDestination(); // 미로 목적지 찾기 메서드
        maze.printStep(); // 정답을 찾기 위한 깊이를 출력
    }

    static int getInt(StringTokenizer st) {
        return parseInt(st.nextToken());
    }
}

class Maze {

    static int[] D_X = {1, 0, -1, 0}; // 좌표
    static int[] D_Y = {0, 1, 0, -1};
    Room[][] rooms; // 미로의 방 이차원 배열

    class Room {
        int x; // x 좌표
        int y; // y 좌표
        int step; // 큐에 들어 간 깊이 
        int num; // 1 or 0
        boolean visited; // 방문 여부

        Room (int x, int y, int num) {
            this.x = x;
            this.y = y;
            this.num = num;
        }
    }

    Maze(int n, int m) {
        rooms = new Room[n][m]; // 생성자로 방의 이차원 배열 생성
    }

    void setInit(int x, int y, int num) {
        rooms[x][y] = new Room(x, y, num); // 배열의 좌표와 숫자로 초기화
    }

    void findDestination() {
        Queue<Room> queue = new ArrayDeque<>(); // queue 생성
        rooms[0][0].visited = true; // 0, 0 좌표 방문 처리
        rooms[0][0].step = 1; // 0, 0 좌표 단계 설정
        queue.add(rooms[0][0]); // 큐 추가
        bfs(queue); // bfs 호출
    }

    void bfs(Queue<Room> queue) {

        while (!queue.isEmpty()) { // queue가 빌 때 까지 반복
            Room room = queue.poll(); // queue에서 room 빼기

            if ((room.x == rooms.length - 1) && (room.y == rooms[0].length - 1)) {
                break; // 만약 마지막 위치에 도달한다면 반복문 종료
            }

            for (int k = 0; k < 4; k++) {
                int newX = D_X[k] + room.x;
                int newY = D_Y[k] + room.y;
                if (validRange(newX, newY)) {
                    rooms[newX][newY].visited = true; // 방문 여부 등록
                    rooms[newX][newY].step = room.step + 1; // queue에 등록된 부모 노드의 깊이 + 1
                    queue.add(rooms[newX][newY]); // 큐 등록
                }
            }
        }

    }

    void printStep() {
        System.out.println(rooms[rooms.length - 1][rooms[0].length - 1].step);
    }


    boolean validRange(int x, int y) {
        return x >= 0 && y >= 0 && x < rooms.length && y < rooms[0].length &&
                !rooms[x][y].visited && rooms[x][y].num == 1;
    }
}

 

<Boj1697>

 

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;

import java.util.StringTokenizer;
import java.util.Queue;
import java.util.ArrayDeque;

import static java.lang.Integer.parseInt;

public class Boj1697 {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        StringTokenizer st = new StringTokenizer(br.readLine());
        int n = parseInt(st.nextToken());
        int k = parseInt(st.nextToken());

        HideAndSeek hideAndSeek = new HideAndSeek(n, k);
        hideAndSeek.findTime();
        hideAndSeek.printTime();
    }
}

class HideAndSeek {

    static final int MAX_DISTANCE = 100001; // 최대 거리 (0, 10만 일 경우)
    Location[] locations = new Location[MAX_DISTANCE];

    int n; // 수빈 위치
    int k; // 동생 위치

    class Location {
        int x; // 위치 좌표
        int t; // 걸린 시간
        boolean visited; // 방문 여부

        Location(int x) {
            this.x = x;
        }
    }

    HideAndSeek(int n, int k) { // 생성자로 Location[] 배열에 location 등록
        this.n = n;
        this.k = k;
        for (int i = 0; i < MAX_DISTANCE; i++) locations[i] = new Location(i);
    }

    void findTime() {
        Queue<Location> queue = new ArrayDeque<>();
        locations[n].visited = true;
        queue.add(locations[n]); // 먼저 수빈 위치의 인스턴스를 큐에 등록
        bfs(queue); // bfs 시작
    }

    void bfs(Queue<Location> queue) {
        int d;
        while(!queue.isEmpty()) {
            Location location = queue.poll(); // 큐에 있는 location 인스턴스 추출

            if (location.x == k) break; // 만약 시간이 같다면 break;

            d = location.x;
            int[] dx = {d + 1, d - 1, d * 2};

            // 만약 현재 위치에서 dx만큼 변한 것이 0보다 크고, 최대 거리를 넘지 않고, 방문 X라면
            for (int z : dx) {
                if (z >= 0 && z < MAX_DISTANCE && !locations[z].visited) {
                    locations[z].visited = true;
                    locations[z].t = location.t + 1; // 다음에 탐색할 location의 시간을 1 추가
                    queue.add(locations[z]);
                }
            }
        }
    }

    void printTime() {
        System.out.println(locations[k].t);
    }
}

 

1697번은 수빈과 동생의 위치를 x로 놓은 후, 우리가 찾아야 하는 t를 step으로 설정합니다!

 

 

2. Queue로 BFS를 구현할 때, step 처리 반드시 설정하기!

 

void bfs(Queue<Room> queue) {

    while (!queue.isEmpty()) {
        Room room = queue.poll();

        if ((room.x == rooms.length - 1) && (room.y == rooms[0].length - 1)) {
            break;
        }

        for (int k = 0; k < 4; k++) {
            int newX = D_X[k] + room.x;
            int newY = D_Y[k] + room.y;
            if (validRange(newX, newY)) {
                rooms[newX][newY].visited = true;
                rooms[newX][newY].step = room.step + 1;
                queue.add(rooms[newX][newY]);
            }
        }
    }
}

 

이 문제는 얼마나 최단 거리로 도착할 수 있는가를 찾는 문제이므로 노드를 삽입한 부모 노드의 깊이에 1을 더하는 것이 중요합니다.

 

BFS는 

1 -> 2 -> 3 순서로 Queue에 넣고 poll하면, 1번 노드가 출력이 되고 1번 노드에 인접한 4, 5 번을 queue에 넣고,

2번 노드를 빼서 인접한 노드 6, 7을 넣게 되면 남은 노드의 처리 순서는 3 -> 4 -> 5 -> 6 -> 7 입니다.

이때, 중요한 점이, 4 5 6 7 의 부모 노드가 무엇인지 모를 수 있으므로 깊이 처리가 어려울 수 있습니다.

따라서, 큐에 등록할 때, 부모 노드 깊이 + 1를 설정해야 큐에서 노드를 뺐을 때, 해당 노드의 깊이를 파악할 수 있습니다.!

그리고, 이 깊이가 바로 최단 거리에 해당하는 답인 것입니다.!

 

이상으로 2178, 1697번의 BFS 문제 정리를 마치도록 하겠습니다.! 감사합니다!

 

안녕하세요. 회사와 함께 성장하고 싶은 KOSE입니다.

 

이번 포스팅은 백준 2667, 1012번의 자바 풀이를 작성하고자 합니다.

두 문제 모두 입력 처리만 다르게 하되 동일한 dfs로 해결하는 문제이므로 함께 정리하도록 하겠습니다.!

 

문제 링크: https://www.acmicpc.net/problem/2667

문제 링크: https://www.acmicpc.net/problem/1012

 

1. 코드 

 

<Boj2667>

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;

import java.util.Collections;
import java.util.List;
import java.util.ArrayList;

public class Boj2667 {
    
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());
        
        Villa villa = new Villa(n);
        for (int i = 0; i < n; i++) {
            String str = br.readLine();
            villa.init(i, str); // villa 초기화
        }
        
        List<Integer> villas = villa.calculateTotalVilla();
        Collections.sort(villas); // villa 정렬

        System.out.println(villas.size()); // 다른 단지 수 출력
        
        StringBuilder sb = new StringBuilder();
        for (Integer v : villas) {
            sb.append(v).append('\n');
        }
        
        System.out.println(sb); // 단지의 하우스 출력
    }    
}

class Villa {
    
    class House {
        int num; // 하우스 1 or 0
        boolean visited; // 방문 여부
        
        house(int num) {
            this.num = num;
        }
    }
    
    static int count; // static으로 단지 개수 출력
    static final int[] D_X = {-1, 0, 1, 0}; // 왼 - 오 좌표
    static final int[] D_Y = {0, 1, 0, -1}; // 상 - 아 좌표
    
    House[][] houses; // 하우스 이차원 배열
    List<Integer> counts = new ArrayList<>(); // count를 담을 list
    
    Villa(int n) {
        houses = new House[n][n];
    } // 생성자
    
    void init(int idx, String str) {
        for (int i = 0; i < houses.length; i++) {
            houses[idx][i] = new house(Character.getNumericValue(str.charAt(i))); // 초기화
        }
    }
    
    List<Integer> calculateTotalVilla() { // 총 빌라 수 구하기
        for (int i = 0; i < houses.length; i++) {
            for (int j = 0; j < houses[0].length; j++) {

                // 한번도 방문하지 않으면서 num이 1인 하우스 dfs
                if (!houses[i][j].visited && houses[i][j].num == 1) {
                    count = 0;
                    dfs(i, j);
                    counts.add(count);
                }
            }
        }
        return counts;
    }

    private void dfs(int x, int y) {
        if ((!houses[x][y].visited) && (houses[x][y].num == 1)) {
            houses[x][y].visited = true; // 방문한 곳 true 처리
            ++count;
            for (int k = 0; k < 4; k++) {
                int newX = D_X[k] + x; // 앞 서 정의한 x 좌표
                int newY = D_Y[k] + y; // 앞 서 정의한 y 좌표
                if (validRange(newX, newY)) dfs(newX, newY); // dfs 재귀
            }
        }
     }

     // house의 배열에 유효한 인덱스인지 판단
    private boolean validRange(int x, int y) {
        return (x >= 0) && (y >= 0) && (x < houses.length) && (y < houses.length);
    }
}

 

 

<Boj1012>

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;

import java.util.List;
import java.util.ArrayList;
import java.util.StringTokenizer;

import static java.lang.Integer.parseInt;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st;

        int t = parseInt(br.readLine());
        int[] result = new int[t];
        int m, n, k;

        for (int i = 0; i < t; i++) {
            st = new StringTokenizer(br.readLine());
            m = getInt(st);
            n = getInt(st);
            k = getInt(st);
            Field field = new Field(m, n);

            for (int j = 0; j < k; j++) {
                st = new StringTokenizer(br.readLine());
                field.cabbages[getInt(st)][getInt(st)] = field.setCabbage();
            }
            field.calculateWhiteBug(m, n);
            result[i] = field.counts.size();
        }

        for (int r : result) {
            System.out.println(r);
        }
    }

    static int getInt(StringTokenizer st) {
        return parseInt(st.nextToken());
    }
}

class Field {

    static final int[] D_X = {1, 0, -1, 0};
    static final int[] D_Y = {0, 1, 0, -1};

    Cabbage[][] cabbages;
    List<Integer> counts = new ArrayList<>();

    class Cabbage {
        int num;
        boolean visited;

        Cabbage(int num) {
            this.num = num;
        }
    }

    Field(int m, int n) {
        cabbages = new Cabbage[m][n];
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                cabbages[i][j] = new Cabbage(0);
            }
        }
    }

    Cabbage setCabbage() {
        return new Cabbage(1);
    }


    void calculateWhiteBug(int m, int n) {
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (validRange(i, j) && !cabbages[i][j].visited && cabbages[i][j].num == 1) {
                    dfs(i, j);
                    counts.add(1);
                }
            }
        }
    }

    void dfs(int x, int y) {
        if (!cabbages[x][y].visited && cabbages[x][y].num == 1) {
            cabbages[x][y].visited = true;
            for (int i = 0; i < 4; i++) {
                int newX = D_X[i] + x;
                int newY = D_Y[i] + y;
                if (validRange(newX, newY)) dfs(newX, newY);
            }
        }
    }

    boolean validRange(int x, int y) {
        return x >= 0 && y >= 0 && x < cabbages.length && y < cabbages[0].length;
    }
}

 

2. 중요한 포인트

 

-  dfs를 적용하기 위해, 방문 가능한 하우스(노드)가 무엇인지 판단하기 위해 visited라는 boolean을 설정해야 합니다.

-  dfs 메서드가 재귀로 호출되므로 newX, newY라는 새로운 좌표로 dfs에 인자로 넣어주어야 합니다. 

-  ArrayIndexOutOfBoundsException이 발생할 수 있으므로 새롭게 생성되는 newX, newY가 x >= 0, y >= 0 그리고
house.length의 길이보다 작아야 houses[][] 배열에 인덱스로 접근할 수 있습니다.

 

 

 

3. 재귀적 원리로 dfs() 활용하기

 

void dfs(int idx) {
    Node root = nodes[idx]; // root node 가져오기
    root.visitedDfs = true; // 방문 처리
    listDfs.add(root.u); // 방문한 것 출력을 위해 배열에 추가
    for (Node n : root.adjacent) {
        if (!n.visitedDfs) {
            n.visitedDfs = true;
            dfs(n.u);
        }
    }
}

 

일반적으로 dfs를 구현하는데 사용되는 샘플 코드는 다음과 같습니다.

nodes[idx]에서 root 노드를 가져온 후, node에 인접한 adjacent 노드들을 가져온 후, 방문 여부를 판단하고 방문하지 않았다면 
방문 여부를 등록한 후 dfs()로 메서드를 다시 호출합니다.

 

이와같은 원리로 이 문제에서도 방문 여부 false를  판단하고 문제에서 추가로 제시한 1인 번호인지를 체크하고
방문 여부를 true로 설정합니다. 이 후 인접한 노드 (여기서는 인접 좌표)를 설정한 후 인접한 노드로 다시 dfs를 적용합니다.

 

private void dfs(int x, int y) {
    if ((!houses[x][y].visited) && (houses[x][y].num == 1)) {
        houses[x][y].visited = true; // 방문한 곳 true 처리
        ++count;
        for (int k = 0; k < 4; k++) {
            int newX = D_X[k] + x; // 앞 서 정의한 x 좌표
            int newY = D_Y[k] + y; // 앞 서 정의한 y 좌표
            if (validRange(newX, newY)) dfs(newX, newY); // dfs 재귀
        }
    }
 }

 

 

이상입니다! 감사합니다 ~!

+ Recent posts