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

 

프로그래머스

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

programmers.co.kr

 

최소 필요 피로도 = 해당 던전을 탐험하기 위해 가지고 있어야 하는 최소한의 피로도(입장)

소모 피로도 = 던전을 탐험한 후 소모되는 피로도

현재 피로도 k 일 때 탐험할 수 있는 최대 던전 수

예시를 살펴보자.

[[80,20], [50,40], [30,10]] 일 때 탐험 순서에 따라 결과가 달라진다는 것을 알 수 있다.

가장 단순한 방법으로는 모든 탐험 순서의 결과를 찾아보는 것이다. 하지만 이는 비효율적인 방법이다.

무작정 모든 탐험 순서를 보기보다는 가능성이 있는 탐험만 보고 가능성이 없다면 빠져나오는 방식을 사용하겠다. 즉 백트랙킹이다.

여기서 가능성을 어떻게 판단할까?

피로도를 기준으로 판단할 수 있다. 현재 피로도가 다음 던전 탐색 최소 필요 피로도보다 클 때만 탐험을 나아가면 된다.

class Solution {
    
    static int answer = -1;
    
    public int solution(int k, int[][] dungeons) {
        dfs(0, k, dungeons, new boolean[dungeons.length]);
        return answer;
    }
    
    private void dfs(int cnt, int k, int[][] dungeons, boolean[] visited) { 
        for (int i = 0; i < dungeons.length; i++) {
            if (!visited[i] && k >= dungeons[i][0]) {
                visited[i] = true;
                dfs(cnt + 1, k - dungeons[i][1], dungeons, visited);
                answer = Math.max(answer, cnt + 1);
                visited[i] = false;
            }
        }
    }
}

'Algorithm > Programmers' 카테고리의 다른 글

Programmers 92342 - 양궁대회  (0) 2024.10.02
Programmers 12952 - N-Queen  (0) 2024.10.01
Programmers 86971 - 전력망을 둘로 나누기  (1) 2024.09.30
Programmers 67259 - 경주로 건설  (0) 2024.09.30
Programmers 12978 - 배달  (0) 2024.09.29

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

 

프로그래머스

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

programmers.co.kr

 

n개의 송전탑이 트리 형태로 연결되어 있다. 여기서 하나의 전선을 끊어서 네트워크를 2개로 분할하려고 한다. 이때 두 전력망이 갖게 되는 송전탑의 개수를 최대한 비슷하게 맞춰야 한다.

두 전력망이 가지고 있는 송전탑 개수의 차이(절댓값)를 return하라.

 

트리 형태이기 때문에 모든 노드가 연결이 되어 있다.

브루트포스를 통해 간선을 하나 제거하고 dfs로 탐색을 하면 하나의 그룹의 송전탑의 개수를 구할 수 있고 n개에서 그 개수를 빼면 나머지 송전탑 그룹의 개수가 결정된다.

이 방법을 지워갈 간선을 하나씩 전부 선택해 가며 반복하면서 차이의 최소를 갱신해 가면 답을 구할 수 있을 것이다.

class Solution {
    
    private int[][] graph;
    
    public int solution(int n, int[][] wires) {
        int answer = Integer.MAX_VALUE;
        initGraph(n, wires);
        for (int[] wire : wires) {
            // 이번 차례 제외할 wire
            disconnectWire(wire);
            int cnt = dfs(1, new boolean[n + 1]);
            answer = Math.min(answer, Math.abs(n - 2 * cnt));
            // 다시 연결
            connectWire(wire);
        }
        return answer;
    }
    
    private int dfs(int v, boolean[] visited) {
        visited[v] = true;
        
        int cnt = 1;
        for (int u = 0; u < visited.length; u++) {
            if (graph[v][u] == 1 && !visited[u]) {
                cnt += dfs(u, visited);
            }
        }
        
        return cnt;
    }
    
    private void initGraph(int n, int[][] wires) {
        graph = new int[n + 1][n + 1];
        
        for (int[] wire : wires) {
            connectWire(wire);
        }
    }
    
    private void disconnectWire(int[] wire) {
        int v = wire[0];
        int u = wire[1];
        graph[v][u] = 0;
        graph[u][v] = 0;
    }
    
    private void connectWire(int[] wire) {
        int v = wire[0];
        int u = wire[1];
        graph[v][u] = 1;
        graph[u][v] = 1;
    }
}

'Algorithm > Programmers' 카테고리의 다른 글

Programmers 12952 - N-Queen  (0) 2024.10.01
Programmers 87946 - 피로도  (0) 2024.10.01
Programmers 67259 - 경주로 건설  (0) 2024.09.30
Programmers 12978 - 배달  (0) 2024.09.29
Programmers 159993 - 미로 탈출  (0) 2024.09.29

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

 

프로그래머스

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

programmers.co.kr

 

N x N 크기의 격자 형태의 board가 존재하고 각 격자의 칸은 0 또는 1로 채워져 있으며, 0은 칸이 비어있음을 1은 해당 칸이 벽으로 채워져 있음을 나타낸다.

시작점은 (0,0) 칸(좌측 상단), 도착점은 (N-1, N-1)(우측 하단)이다.

직선 도로와 코너가 존재한다. 직선 도로는 상하 또는 좌우로 연결한 경주로를 의미하고 두 직선 도로가 직각으로 만다는 지점을 코너라고 한다.

직선 도로 하나를 만들 때는 100원이 소요되며, 코너를 하나 만들 때는 500원이 추가로 든다.

경주로를 건설하는데 들어가는 최소 비용은?

 

가중치가 존재하는 최단 거리 문제이다. 최소 비용을 구하기 위해서는 추가 금액이 들어가는 코너를 최소한으로 써야지 가능할 것 같다.

코너의 판정은 어떻게 해야 할까? 코너는 결국 이동 방향이 변경됐다는 것이다. 따라서 직전 이동 방향과 이번 이동 뱡향을 알면 코너인지를 판단할 수 있다. 즉 이전과 방향이 다르면 코너다.

좌우의 경우도 방향이 다르다. 하지만 이 경우는 코너가 아니다. 그런데 어차피 이 경우는 생각하지 않아도 된다. 최단거리를 찾는데 돌아가는 경우는 없다.

풀이를 생각해보자. 우선 가중치가 있는 최단거리를 구해야 하는 문제이다. 다익스트라로 풀 수 있을까?

다익스트라의 PriorityQueue에 넣을 때 방향도 함께 넣어서 방향별로 다른 경로로 판정하면 가능할 것 같다.

import java.util.*;

class Solution {
    
    static class Node {
        int r;
        int c;
        int dir;
        int cost;
        
        public Node(int r, int c, int dir, int cost) {
            this.r = r;
            this.c = c;
            this.dir = dir;
            this.cost = cost;
        }
    }
    
    static final int INF = Integer.MAX_VALUE;
    static int[] dr = {-1, 1, 0, 0}; //상하좌우
    static int[] dc = {0, 0, -1, 1};
    
    static int[][] distances;
    
    public int solution(int[][] board) {
        int N = board.length;
        distances = new int[N][N];
        for (int[] row : distances) {
            Arrays.fill(row, INF);
        }
        
        distances[0][0] = 0;
        PriorityQueue<Node> pq = new PriorityQueue<>(Comparator.comparingInt(n -> n.cost));
        pq.add(new Node(0, 0, 1, 0));
        pq.add(new Node(0, 0, 3, 0));
        
        while(!pq.isEmpty()) {
            Node curr = pq.poll();
            int r = curr.r;
            int c = curr.c;
            int dir = curr.dir;
            int cost = curr.cost;
            
            if (distances[r][c] < cost) continue;
            
            for (int d = 0; d < 4; d++) {
                int weight = 100;
                int nr = r + dr[d];
                int nc = c + dc[d];
                if (nr < 0 || nr >= N || nc < 0 || nc >= N) continue;
                if (board[nr][nc] == 1) continue;
                if (dir != d) {
                    weight += 500;
                }
                
                if (distances[nr][nc] > distances[r][c] + weight) {
                    distances[nr][nc] = distances[r][c] + weight;
                    pq.add(new Node(nr, nc, d, distances[nr][nc]));
                }
            }
        }
        
        int answer = distances[N-1][N-1];
        return answer;
    }
}

이렇게 풀었는데 오답이 됐다. 왜 틀렸는지 분석해 보자.

가장 큰 문제는 int [][] distances 배열이다.

나는 처음에는 직선으로 도착하거나 코너로 도착하거나 둘 중 하나이니까 그 두 경우를 전부 pq에 넣으면 어차피 저렴한 걸로 갱신되지 않을까 생각했다. 그런데 이렇게 풀게 되면 그 지점까지의 최솟값이 최종적인 최솟값이라고 보장이 돼야 한다. 하지만 이 문제는 그렇지 않다.

이렇게 관리하게 되면 같은 위치라도 다른 방향으로 도착했을 때의 비용 차이를 반영하지 못한다.

결과적으로, 특정 위치에 더 높은 비용으로 도착했지만, 방향이 달라서 이후 경로에서 더 낮은 총비용을 가질 수 있는 경우를 놓치게 되는 것이다.

이를 해결하기 위해 int [][][] distances로 3차원으로 방향까지 관리하도록 하겠다.

import java.util.*;

class Solution {
    
    static class Node {
        int r;
        int c;
        int dir;
        int cost;
        
        public Node(int r, int c, int dir, int cost) {
            this.r = r;
            this.c = c;
            this.dir = dir;
            this.cost = cost;
        }
    }
    
    static final int INF = Integer.MAX_VALUE;
    static int[] dr = {-1, 1, 0, 0}; //상하좌우
    static int[] dc = {0, 0, -1, 1};
    
    static int[][][] distances;
    
    public int solution(int[][] board) {
        int N = board.length;
        distances = new int[4][N][N];
        for (int d = 0; d < 4; d++) {
            for (int[] row : distances[d]) {
                Arrays.fill(row, INF);
            }
        }
        
        PriorityQueue<Node> pq = new PriorityQueue<>(Comparator.comparingInt(n -> n.cost));
        for (int d = 0; d < 4; d++) {
            distances[d][0][0] = 0;
            pq.add(new Node(0, 0, d, 0));
        }
        
        while(!pq.isEmpty()) {
            Node curr = pq.poll();
            int r = curr.r;
            int c = curr.c;
            int dir = curr.dir;
            int cost = curr.cost;
            
            if (distances[dir][r][c] < cost) continue;
            
            for (int d = 0; d < 4; d++) {
                int nr = r + dr[d];
                int nc = c + dc[d];
                int weight = 100;
                if (nr < 0 || nr >= N || nc < 0 || nc >= N) continue;
                if (board[nr][nc] == 1) continue;
                
                if (dir != d) {
                    weight += 500;
                } 
                
                if (distances[d][nr][nc] > distances[dir][r][c] + weight) {
                    distances[d][nr][nc] = distances[dir][r][c] + weight;
                    pq.add(new Node(nr, nc, d, distances[d][nr][nc]));
                }
            }
        }
        
        int answer = INF;
        for (int d = 0; d < 4; d++) {
            answer = Math.min(answer, distances[d][N-1][N-1]);
        }
        return answer;
    }
}

이를 통해 문제를 해결할 수 있었다.

'Algorithm > Programmers' 카테고리의 다른 글

Programmers 87946 - 피로도  (0) 2024.10.01
Programmers 86971 - 전력망을 둘로 나누기  (1) 2024.09.30
Programmers 12978 - 배달  (0) 2024.09.29
Programmers 159993 - 미로 탈출  (0) 2024.09.29
Programmers 43162 - 네트워크  (0) 2024.09.29

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

 

프로그래머스

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

programmers.co.kr

 

N개의 마을 중 K 시간 이하로 배달이 가능한 마을에서만 주문을 받는다.

시작점은 1번 마을이다.

가중치가 존재하는 최단거리 계산이다. 즉 다익스트라 알고리즘을 사용하면 될 것 같다.

1번으로부터 각 노드까지의 최단 거리를 계산하고 해당 최단 거리가 K 이하인 곳들의 개수를 세면 된다.

import java.util.*;
import java.util.stream.*;

class Solution {
    
    static class Node {
        int id;
        int distance;
        
        public Node(int id, int distance) {
            this.id = id;
            this.distance = distance;
        }
    }
    
    static final int INF = Integer.MAX_VALUE;
    static int[] distances;
    static Map<Integer, List<Node>> graph = new HashMap<>();
    
    public int solution(int N, int[][] road, int K) {
        distances = new int[N + 1];
        Arrays.fill(distances, INF);
        for (int i = 0; i <= N; i++) {
            graph.put(i, new ArrayList<>());
        }
        
        for (int[] edge : road) {
            graph.get(edge[0]).add(new Node(edge[1], edge[2]));
            graph.get(edge[1]).add(new Node(edge[0], edge[2]));
        }
    
        djikstra(1);

        return (int)IntStream.range(1, N + 1)
                        .filter(i -> distances[i] <= K)
                        .count();
    }
    
    private void djikstra(int start) {
        PriorityQueue<Node> pq = new PriorityQueue<>(Comparator.comparingInt(n -> n.distance));
        
        pq.add(new Node(start, 0));
        distances[start] = 0;
        
        while (!pq.isEmpty()) {
            Node curr = pq.poll();
            int u = curr.id;
            
            if (distances[u] < curr.distance) continue;
           
            for (Node next : graph.get(u)) {
                int v = next.id;
                int weight = next.distance;
                
                if (distances[v] > distances[u] + weight) {
                    distances[v] = distances[u] + weight;
                    pq.add(new Node(v, distances[v]));
                }
            }
        }
    }
}

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

 

프로그래머스

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

programmers.co.kr

 

출발지 → 레버 → 출구 순으로 방문을 해야만 탈출할 수 있다.

미로 1칸을 이동하는 것을 1초라고 가정했을 때 최대한 빠르게 미로를 빠져나가는 데 걸리는 시간을 구하는 것이다.

즉 최단거리를 구해야 하고 BFS를 사용하면 될 것 같다.

탈출하지 못한 경우 -1을 반환하면 된다.

우선 주어진 배열은 String [ ] maps이다. 문자열로 “SOOOL” 이런 식으로 값이 들어가 있다.

S는 출발지, E 출구, L 레버, O 통로, X 벽이다.

문자열이라는 것은 결국 char []을 다루는 것과 동일하다.

또한 시간을 저장하기 위한 별도의 배열이 필요하다 이 배열은 시작 위치에서부터 탐색이 될수록 1씩 증가시켜 가며 값을 저장할 것이다.

출발지 → 레버로 도달하지 못하면 -1을 반환한다.

그 후 레버 → 출구 과정에서 도달하지 못하면 마찬가지로 -1을 반환한다.

최종 풀이는 다음과 같다.

  1. S에서 L까지 BFS를 통해 걸리는 최소 시간을 구한다.
  2. L에서 O를 목표로 BFS를 통해 걸리는 최소 시간을 구한다.
  3. 두 최소 시간을 통해 정답을 반환한다.
import java.util.*;

class Solution {
    
    static class Point {
        int r;
        int c;
        
        public Point(int r, int c) {
            this.r = r;
            this.c = c;
        }
        
        // 편의상 간단하게 구현
        @Override
        public boolean equals(Object o) {
            Point other = (Point) o;
            return r == other.r && c == other.c;
        }
    }
    static final int[] DR = {-1, 1, 0, 0};
    static final int[] DC = {0, 0, -1, 1};
    static Point start;
    static Point lever;
    static Point goal;
    
    public int solution(String[] maps) {
        int startToLever = 0;
        int leverToGoal = 0;
        
        for (int row = 0; row < maps.length; row++) {
            for (int col = 0; col < maps[row].length(); col++) {
                switch (maps[row].charAt(col)) {
                    case 'S' -> start = new Point(row, col);
                    case 'L' -> lever = new Point(row, col);
                    case 'E' -> goal = new Point(row, col);
                }
            }
        }
        
        startToLever = bfs(start, lever, maps);
        leverToGoal = bfs(lever, goal, maps);
        
        // 도달하지 못하는 경우
        if (startToLever == 0 || leverToGoal == 0) return -1;
        
        return startToLever + leverToGoal;
    }
    
    private int bfs(Point start, Point goal, String[] maps) {
        
        int[][] times = new int[maps.length][maps[0].length()];
        Queue<Point> q = new LinkedList<>();
        q.add(start);
        
        while (!q.isEmpty()) {
            Point curr = q.poll();
            
            if (curr.equals(goal)) {
                return times[curr.r][curr.c];
            }
            
            for (int d = 0; d < 4; d++) {
                int nr = curr.r + DR[d];
                int nc = curr.c + DC[d];
                
                if (nr < 0 || nr >= maps.length || nc < 0 || nc >= maps[nr].length()) continue;
                if (maps[nr].charAt(nc) == 'X') continue;
                if (times[nr][nc] != 0) continue;
                
                times[nr][nc] = times[curr.r][curr.c] + 1;
                q.add(new Point(nr, nc));
            }
        }
        
        return 0; // goal에 도달하지 못한 경우
    }
}

 

방문 판단을 times 배열의 값으로 하고 있는데 시작점을 별도로 방문처리 하지 않았다. 

이는 어차피 방문 지점을 BFS로 재방문한다 해도 답에 영향이 없기 때문에 최종 답 처리 편의를 위해 방문처리를 생략했다.

'Algorithm > Programmers' 카테고리의 다른 글

Programmers 67259 - 경주로 건설  (0) 2024.09.30
Programmers 12978 - 배달  (0) 2024.09.29
Programmers 43162 - 네트워크  (0) 2024.09.29
Programmers 1844 - 게임 맵 최단거리  (2) 2024.09.29
Programmers 42861 - 섬 연결하기  (0) 2024.09.28

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

 

프로그래머스

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

programmers.co.kr

 

네트워크의 개수를 판정하는 문제이다.

여기서 네트워크란 직/간접적으로 연결 돼 있는 것들의 집합이다.

우선 n개의 노드가 있기 때문에 이 노드들의 방문 여부를 저장할 배열을 만든다.

그리고 시작점을 1~ n으로 해서 해당 시작점이 방문되지 않았다면 dfs를 한다.

여기에서 dfs가 진행된 횟수가 곧 네트워크의 개수를 알 수 있다.

왜냐하면 dfs가 끝났는데도 새롭게 시작한다는 것은 연결된 네트워크가 아니라 새로운 네트워크의 시작이라는 의미이기 때문이다.

class Solution {
    public int solution(int n, int[][] computers) {
        boolean[] visited = new boolean[n];
        
        int answer = 0;
        
        for (int start = 0; start < n; start++) {
            if (visited[start]) continue;
            dfs(start, computers, visited);
            answer++;
        }
        
        return answer;
    }
    
    private void dfs(int v, int[][] computers, boolean[] visited) {
        
        visited[v] = true;
        
        for (int u = 0; u < computers[v].length; u++) {
            if (computers[v][u] == 1 && !visited[u]) {
                dfs(u, computers, visited);
            }
        }
    }
}

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

 

프로그래머스

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

programmers.co.kr

 

간단한 bfs 문제이다.

상대 팀 진영에 ‘최대한 빨리’ 도착하는 것이 유리하다.

→ 최단거리 문제다. bfs를 통해서 최단 거리를 구할 수 있다.

주어진 데이터가 int [][] maps인데 0은 벽 1은 벽이 없는 자리를 나타낸다.

여기서 방문 가능한 점을 탐색하면서 bfs를 하면 된다.

또한 해당 맵에서 위치 값을 변경해 가면서 처리하면 별도의 방문 배열이 필요 없어진다.

import java.util.*;

class Solution {
    static int[] DR = {-1, 1, 0, 0}; // 상하좌우 기준
    static int[] DC = {0, 0, -1, 1};
    
    public int solution(int[][] maps) {
        
        int m = maps.length - 1;
        int n = maps[0].length - 1;
        
        Queue<int[]> q = new LinkedList<>();
        q.add(new int[]{0, 0}); // 시작점
        
        while (!q.isEmpty()) {
            int[] curr = q.poll();
            int r = curr[0];
            int c = curr[1];
            
            if (r == m && c == n) {
                return maps[r][c]; // 이미 도착지에 도달한 상황
            }
            
            for (int d = 0; d < 4; d++) {
                int nr = r + DR[d];
                int nc = c + DC[d];
                
                if (nr < 0 || nr > m || nc < 0 || nc > n) continue;
                if (maps[nr][nc] != 1) continue;
                
                maps[nr][nc] = maps[r][c] + 1;
                q.add(new int[]{nr, nc});
            }
        }
        
        return maps[m][n] == 1 ? -1 : maps[m][n];
    }
}

'Algorithm > Programmers' 카테고리의 다른 글

Programmers 12978 - 배달  (0) 2024.09.29
Programmers 159993 - 미로 탈출  (0) 2024.09.29
Programmers 43162 - 네트워크  (0) 2024.09.29
Programmers 42861 - 섬 연결하기  (0) 2024.09.28
Programmers 12981 - 영어 끝말잇기  (0) 2024.09.27

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

 

프로그래머스

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

programmers.co.kr

 

모든 섬이 통행 가능하도록 다리를 놓을 때 최소 비용을 구하는 문제이다.

주어진 정보는 모든 섬의 개수 n, 두 섬의 번호와 비용이 있는 costs 배열이다.

최소 비용을 구하는 문제이기 때문에 먼저 떠오르는 것은 cost가 저렴한 순으로 꺼내서 다리를 설치하는 것이다.

이때 만약 불필요한 다리라면 건설하지 않아야 한다. 여기서 불필요하다는 뜻은 해당 다리가 없어도 방문이 가능한 경우를 뜻한다.

여기서 이 판단을 하는 방법으로 집합을 쓰겠다. 같은 집합에 속한다면 즉 루트가 같다면 해당 집합은 모두 방문이 가능한 상태가 된다.

알고리즘의 초안은 다음과 같다.

  1. 최소 비용의 다리를 꺼낸다. → PriorityQueue를 사용하면 간단해진다.
  2. 이 다리에 추가되는 섬이 이미 같은 집합에 존재하면 다리를 설치하지 않는다. → 설치되는 경우 두 섬을 Union 연산을 통해 같은 집합으로 만든다.
  3. pq가 비게 될 때까지 반복한다.

결국 Union-Find만 잘 구현하면 해결되는 문제다.

우선 Union-Find의 코드부터 보고 이해해 보자.

Union연산은 두 집합을 하나로 합치는 연산이고 Find는 특정 요소가 속하는 집합의 루트(대표)를 찾는 연산이다. 이 알고리즘은 주로 트리 구조를 사용하여 집합을 표현하게 된다.

public class UnionFind {
    private int[] parent; // 각 요소의 부모를 저장하는 배열
    private int[] rank;   // 트리의 높이를 저장하는 배열

    // 생성자: n개의 요소를 각각 독립된 집합으로 초기화
    public UnionFind(int n) {
        parent = new int[n];
        rank = new int[n];
        for(int i = 0; i < n; i++) {
            parent[i] = i; // 초기에는 각 요소가 자기 자신을 부모로 가짐
            rank[i] = 1;   // 초기 트리의 높이는 1로 설정. 0으로 하는 경우도 있다.
        }
    }

    // Find 연산: x가 속한 집합의 대표(루트)를 찾음
    public int find(int x) {
        if(parent[x] != x) {
            parent[x] = find(parent[x]); // 경로 압축
        }
        return parent[x];
    }

    // Union 연산: x가 속한 집합과 y가 속한 집합을 합침
    public void union(int x, int y) {
        int rootX = find(x);
        int rootY = find(y);

        if(rootX == rootY) {
            return; // 이미 같은 집합에 속해 있음
        }

        // 랭크를 비교하여 트리의 높이를 최소화
        if(rank[rootX] < rank[rootY]) {
            parent[rootX] = rootY;
        } else if(rank[rootX] > rank[rootY]) {
            parent[rootY] = rootX;
        } else {
            parent[rootY] = rootX;
            rank[rootX] += 1; // 트리의 높이가 증가
        }
    }

    // 두 요소가 같은 집합에 속해 있는지 확인
    public boolean connected(int x, int y) {
        return find(x) == find(y);
    }
}

이해하기 어려운 알고리즘은 아니다. 여기서 주목해야 할 부분이 두 가지 있다.

  1. 경로 압축 parent 배열은 자신의 부모 노드를 가지고 있다. parent [idx]가 idx의 부모를 뜻한다. 하지만 바로 자신의 부모를 가지고 있으면 탐색에 시간이 증가하게 된다. 이를 효율화하기 위해 자신의 부모가 아닌 집합의 루트를 가지고 있게 하는 방식이 경로 압축이다.
  2. Rank 개념 트리의 높이가 커질수록 연산이 비효율적이 됩니다. 따라서 Rank의 개념을 도입해 높이를 최소화한다. Union 연산 시, 두 집합의 트리 높이를 비교하여 작은 트리를 큰 트리 아래에 합친다. 높이가 같다면 어디에 합쳐도 상관없다.

이 알고리즘을 통해 문제를 다음과 같이 풀 수 있습니다.

import java.util.*;

class Solution {
    
    public int solution(int n, int[][] costs) {
        
        int[] parent = new int[n];
        int[] rank = new int[n];
        for (int i = 0; i < n; i++) {
            parent[i] = i;
        }
        
        int answer = 0;
        
        PriorityQueue<int[]> pq = new PriorityQueue<>(Comparator.comparingInt(a -> a[2]));
        for (int[] info : costs) {
            pq.add(info);
        }
        
        while (!pq.isEmpty()) {
            int[] info = pq.poll();
            int id1 = info[0];
            int id2 = info[1];
            int cost = info[2];
            
            // 이미 같은 집합에 속해있다면 다리를 놓는것은 낭비다.
            if (find(id1, parent) == find(id2, parent)) continue;
            
            union(id1, id2, parent, rank);
            
            answer += cost;
        }
        

        return answer;
    }
    
    private int find(int x, int[] parent) {
        if (x != parent[x]) {
            parent[x] = find(parent[x], parent);
        }
        return parent[x];
    }
    
    private void union(int x, int y, int[] parent, int[] rank) {
        int rootX = find(x, parent);
        int rootY = find(y, parent);
        
        if (rootX == rootY) return;
        
        if (rank[rootX] < rank[rootY]) {
            parent[rootX] = rootY;
        } else if (rank[rootX] > rank[rootY]) {
            parent[rootY] = rootX;
        } else {
            parent[rootY] = rootX;
            rank[rootX]++;
        }
    }
}

'Algorithm > Programmers' 카테고리의 다른 글

Programmers 12978 - 배달  (0) 2024.09.29
Programmers 159993 - 미로 탈출  (0) 2024.09.29
Programmers 43162 - 네트워크  (0) 2024.09.29
Programmers 1844 - 게임 맵 최단거리  (2) 2024.09.29
Programmers 12981 - 영어 끝말잇기  (0) 2024.09.27

코딩테스트 연습 - 영어 끝말잇기 | 프로그래머스 스쿨 (programmers.co.kr)

 

문제 조건

  1. 1번부터 번호 순서대로 한 사람씩 차례대로 단어를 말합니다.
  2. 마지막 사람이 단어를 말한 다음에는 다시 1번부터 시작
  3. 앞사람이 말한 단어의 마지막 문자로 시작하는 단어.
  4. 이미 등장한 단어는 사용 불가
  5. 한 글자는 안된다
  6. 탈락자가 생기지 않는다면 [0, 0]을 반환한다.

이때 가장 먼저 탈락하는 사람의 번호와 그 사람이 자신의 몇 번째 차례에 탈락하는가.

문제의 흐름대로 구현

문제 조건을 읽으며 정리한 단서들을 조합해 보자.

우선은 앞에서부터 순회하며 읽는다.

또한 순환하고 있기 때문에 % 연산을 사용한다.

문자의 비교를 위해 charAt을 사용해서 비교를 한다.

이미 등장했던 단어를 빠르게 검색하기 위해 Hash 기반의 Set을 사용하면 될 것 같다.

예외로 한 글자 조건이 있다.

자신의 턴을 관리해줘야 한다. 이는 Map을 통해 자신의 번호 : 턴으로 관리를 하면 빠르게 찾고 수정할 수 있다.

import java.util.*;

class Solution {
    public int[] solution(int n, String[] words) {
        Map<Integer, Integer> turn = new HashMap<>();
        Set<String> wordSet = new HashSet<>();
        for (int i = 1; i <= n; i++) {
            turn.put(i, 1);
        }
        
        // 첫 턴 예외
        turn.put(1, 2);
        wordSet.add(words[0]);
        int id = 1;
        boolean isEnd = true;
        
        for (int i = 1; i < words.length; i++) {
            id = i % n + 1;
            String prevWord = words[i - 1];
            String word = words[i];
            
            // 탈락 조건
            if (word.length() == 1 ||
                wordSet.contains(word) ||
                prevWord.charAt(prevWord.length() - 1) != word.charAt(0)) {
                isEnd = false;
                break;
            }
        
            turn.put(id, turn.get(id) + 1);
            wordSet.add(word);
        }
        
        if (isEnd) return new int[]{0, 0};
        
        int[] answer = {id, turn.get(id)};
        
        return answer;
    }
}

코드 개선

문제의 흐름을 따라서 구현하다 보면 O(N)의 시간복잡도로 풀이가 가능하다.

이 풀이에서 따져보자면 turn을 관리하고 있는 Map 같은 경우에는 굳이 필요하지 않다.

그리고 첫 턴을 처리하는 것이 코드의 명확성을 떨어뜨린다고 생각한다.

n을 통해서 현재 턴을 (i/n + 1)로 처리하면 공간복잡도를 개선할 수 있을 것 같다.

또한 리턴을 break 시점에 하는 것이 더 빠른 명확한 처리가 될 것 같다.

문제 조건에서 1글자 단어는 인정되지 않는다고 했는데 제한 사항을 보면 단어의 길이가 2 이상 50 이하라는 제한이 있다. 따라서 1글자 검증 과정을 불필요하다.

import java.util.*;

class Solution {
    public int[] solution(int n, String[] words) {
        Set<String> usedWords = new HashSet<>();
        usedWords.add(words[0]);
        
        for (int i = 1; i < words.length; i++) {
            if (usedWords.contains(words[i]) ||
                words[i].charAt(0) != words[i-1].charAt(words[i - 1].length() - 1)) {
                return new int[]{(i % n) + 1, (i / n) + 1};
            }
            usedWords.add(words[i]);
        }
        
        return new int[]{0, 0};
    }
}

개선사항을 통해 훨씬 읽기 좋은 코드가 됐다고 생각한다.

'Algorithm > Programmers' 카테고리의 다른 글

Programmers 12978 - 배달  (0) 2024.09.29
Programmers 159993 - 미로 탈출  (0) 2024.09.29
Programmers 43162 - 네트워크  (0) 2024.09.29
Programmers 1844 - 게임 맵 최단거리  (2) 2024.09.29
Programmers 42861 - 섬 연결하기  (0) 2024.09.28

+ Recent posts