TIL-sparta

[TIL] 프로그래머스 - 피로도 문제 풀이 (Java, DFS)

Megadr0ne 2024. 5. 25. 21:59

 > 지난 주에 배운 내용을 토대로 주말 동안 아이템 시뮬레이터 과제 작업을 진행하고, 작업 전 후로 코딩 테스트 문제를 조금 풀었다. 그 중 마지막으로 푼 '피로도' 문제의 풀이를 적어보았다.

 

학습 키워드: Java, DFS

 

87946 - 피로도

1) 문제 설명 (원문은 링크 참고):

 

프로그래머스

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

programmers.co.kr

 문제 요약: 게임 속 피로도 시스템을 모방한 문제로, dungeons 2차원 배열의 각 항목에 담긴 1차원 배열이 던전의 정보이며, 각 던전 정보는 [최소 필요 피로도, 소모 피로도] 의 두 가지 element로 구성되어 있다. 최소 필요 피로도는 진입에 필요한 요구량으로, 요구량 미만의 피로도를 가진 경우 진입이 불가능하다. 요구량이 충족되는 경우 현재 피로도 k에서 소모 피로도 만큼을 소모하여 던전에 진입할 수 있다. 최대한 많은 종류의 던전을 방문하는 경우의 방문 수를 구하는 문제다.

 

 조건: 1 <= k <= 5000, 1 <= 소모 피로도 <= 최소 필요 피로도 <= 1000, 던전 피로도 수치 중복 있음

 

 

2) 제출 코드:

class Solution {
    private int[][] dungeons;
    private int max = 0;
    
    public int solution(int k, int[][] dungeons) {
        this.dungeons = dungeons;
        boolean[] visited = new boolean[dungeons.length];
        
        for (int i = 0; i < dungeons.length; i++) {
            visited[i] = true;
            enterTheDungeon(k, 0, i, visited);
            visited[i] = false;
        }
        
        return max;
    }
    
    private void enterTheDungeon(int k, int depth, int dungeon, boolean[] visited) {
        if (k < dungeons[dungeon][0]){
            if (max < depth) max = depth;
            return;
        }
        k -= dungeons[dungeon][1];
        depth++;
        
        for (int i = 0; i < visited.length; i++) {
            if (visited[i]) continue;
            visited[i] = true;
            enterTheDungeon(k, depth, i, visited);
            visited[i] = false;
        }
        
        
        if (max < depth) max = depth;
    }
}

 DFS(Depth-First-Search)로 푸는 문제인데, visited[i]의 값을 false로 되돌리지 않는 바람에 시간을 많이 쓰게됐다. 기본적으로 각각의 노드에서 인접한 노드를 방문하는 것을 recursion 형식으로 구성하는데, 이전에 방문한 노드를 또 방문해서는 안되기 때문에 boolean 배열 visited를 생성하여 이동 전후로 visited 상태를 변경해주는 작업을 해준다. 실제로 노드로 연결되어 있지는 않기 때문에 배열을 순회하면서 이미 방문한(visited[i] == true) 노드는 continue로 넘어가줘야 한다.

 

 recursion으로 다음 던전을 방문할 때 마다 최소 피로도 요구치를 충족하는지 확인해주고, 충족하지 않는다면 return, 충족한다면 소모 피로도 만큼을 k에서 빼준다. return을 하는 시점에서 최대 깊이와 현재 깊이를 비교하여 큰 값으로 max를 갱신해주면 최대 몇 군데의 던전을 방문할 수 있는지를 알 수 있다.

 

 

3) 수정한 코드:

 처음에 코드를 좀 난잡하게 작성하는 바람에 중복되는 부분이 생겨서 모양이 조금 이상해졌는데, 루프 부분을 합치고 불필요한 계산을 줄여 아래와 같이 다듬어 주면 가독성도 높아지고 동작 시간 또한 약간 절약할 수 있다.

class Solution {
    private int[][] dungeons;
    private int max = 0;
    
    public int solution(int k, int[][] dungeons) {
        this.dungeons = dungeons;
        boolean[] visited = new boolean[dungeons.length];
        enterTheDungeon(k, 0, visited);
        return max;
    }
    
    private void enterTheDungeon(int k, int depth, boolean[] visited) {
        for (int i = 0; i < visited.length; i++) {
            if (visited[i] || k < dungeons[i][0]) continue;
            visited[i] = true;
            enterTheDungeon(k - dungeons[i][1], depth + 1, visited);
            visited[i] = false;
        }
        
        if (max < depth) max = depth;
    }
}

 기존 코드는 가장 바깥에 해당하는 루프가 밖으로 분리되어 있어서 if문을 루프 밖에 적었으나, 둘을 합치게 되면 첫 루프의 피로도 체크를 위해서 if문을 루프 안으로 넣어줘야 한다. 또한 파라미터의 dungeon 번호는 enterTheDungeon 함수의 루프 속에 if를 옮기게 되면서 던전 번호인 루프의 index i를 참조할 수 있기 때문에 더 이상 인자로 받을 필요가 없어진다.

 


4) 결과:

 성능 요약: 메모리: 98.6 MB, 시간: 0.08 ms

 

728x90