새소식

Algorithm/Programmers

[Java][프로그래머스] 72413, 합승 택시 요금

  • -
728x90
 

프로그래머스

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

programmers.co.kr

🇱🇻3️⃣, 합승 택시 요금[72413]

 

🧀 프로세스 

[문제 설명]

밤늦게 귀가할 때 안전을 위해 항상 택시를 이용하던 무지는 최근 야근이 잦아져 택시를 더 많이 이용하게 되어 택시비를 아낄 수 있는 방법을 고민하고 있습니다. "무지"는 자신이 택시를 이용할 때 동료인 어피치 역시 자신과 비슷한 방향으로 가는 택시를 종종 이용하는 것을 알게 되었습니다. "무지"는 "어피치"와 귀가 방향이 비슷하여 택시 합승을 적절히 이용하면 택시요금을 얼마나 아낄 수 있을 지 계산해 보고 "어피치"에게 합승을 제안해 보려고 합니다.

 

 

위 예시 그림은 택시가 이동 가능한 반경에 있는 6개 지점 사이의 이동 가능한 택시노선과 예상요금을 보여주고 있습니다.
그림에서 A와 B 두 사람은 출발지점인 4번 지점에서 출발해서 택시를 타고 귀가하려고 합니다. A의 집은 6번 지점에 있으며 B의 집은 2번 지점에 있고 두 사람이 모두 귀가하는 데 소요되는 예상 최저 택시요금이 얼마인 지 계산하려고 합니다.

그림의 원은 지점을 나타내며 원 안의 숫자는 지점 번호를 나타냅니다.
지점이 n개일 때, 지점 번호는 1부터 n까지 사용됩니다.
지점 간에 택시가 이동할 수 있는 경로를 간선이라 하며, 간선에 표시된 숫자는 두 지점 사이의 예상 택시요금을 나타냅니다.
간선은 편의 상 직선으로 표시되어 있습니다.
위 그림 예시에서, 4번 지점에서 1번 지점으로(4→1) 가거나, 1번 지점에서 4번 지점으로(1→4) 갈 때 예상 택시요금은 10원으로 동일하며 이동 방향에 따라 달라지지 않습니다.
예상되는 최저 택시요금은 다음과 같이 계산됩니다.
4→1→5 : A, B가 합승하여 택시를 이용합니다. 예상 택시요금은 10 + 24 = 34원 입니다.
5→6 : A가 혼자 택시를 이용합니다. 예상 택시요금은 2원 입니다.
5→3→2 : B가 혼자 택시를 이용합니다. 예상 택시요금은 24 + 22 = 46원 입니다.
A, B 모두 귀가 완료까지 예상되는 최저 택시요금은 34 + 2 + 46 = 82원 입니다.

 

[문제]
지점의 개수 n, 출발지점을 나타내는 s, A의 도착지점을 나타내는 a, B의 도착지점을 나타내는 b, 지점 사이의 예상 택시요금을 나타내는 fares가 매개변수로 주어집니다. 이때, A, B 두 사람이 s에서 출발해서 각각의 도착 지점까지 택시를 타고 간다고 가정할 때, 최저 예상 택시요금을 계산해서 return 하도록 solution 함수를 완성해 주세요.
만약, 아예 합승을 하지 않고 각자 이동하는 경우의 예상 택시요금이 더 낮다면, 합승을 하지 않아도 됩니다.

[제한사항]
지점갯수 n은 3 이상 200 이하인 자연수입니다.
지점 s, a, b는 1 이상 n 이하인 자연수이며, 각기 서로 다른 값입니다.
즉, 출발지점, A의 도착지점, B의 도착지점은 서로 겹치지 않습니다.
fares는 2차원 정수 배열입니다.
fares 배열의 크기는 2 이상 n x (n-1) / 2 이하입니다.
예를들어, n = 6이라면 fares 배열의 크기는 2 이상 15 이하입니다. (6 x 5 / 2 = 15)
fares 배열의 각 행은 [c, d, f] 형태입니다.
c지점과 d지점 사이의 예상 택시요금이 f원이라는 뜻입니다.
지점 c, d는 1 이상 n 이하인 자연수이며, 각기 서로 다른 값입니다.
요금 f는 1 이상 100,000 이하인 자연수입니다.
fares 배열에 두 지점 간 예상 택시요금은 1개만 주어집니다. 즉, [c, d, f]가 있다면 [d, c, f]는 주어지지 않습니다.
출발지점 s에서 도착지점 a와 b로 가는 경로가 존재하는 경우만 입력으로 주어집니다.

🔽 문제풀이

이번 문제는 두개의 풀이가 가능하다. 처음 플로이드 워셜로 접근해서 문제를 풀이했지만 초기화 MAX 값을 987654321 로 설정했다가 한시간을 삽질하고 다익스트라 풀이까지 도전했던 문제이다. 두가지 풀이 모두 소개하겠다.

 

1️⃣ 플로이드 워셜

int[][] adj = new int[n+1][n+1];
for(int i=1; i<=n; i++) {
        Arrays.fill(adj[i], INF);
        adj[i][i] = 0;
}

for(int[] f : fares)
    adj[f[0]][f[1]] = adj[f[1]][f[0]] = f[2];

// 플로이드 워셜
for(int i=1; i<=n; i++) {
    for(int j=1; j<=n; j++) {
        for(int k=1; k<=n; k++) {
                adj[j][k] = Math.min(adj[j][k], adj[j][i] + adj[i][k]);
        }
    }
}

 

우선 플로이드 워셜을 통해 두 노드를 가는 루트에 있어서 최단 거리를 갱신하며 adj 의 배열을 채워간다. 또한 이는 양방향 그래프임에 주의한다. 

 

int min = adj[s][a] + adj[s][b];
for(int i=1; i<=n; i++) {
    min = Math.min(min, adj[s][i] + adj[i][a] + adj[i][b]);
}

return min;

 

다음으로 min 을 adj[s][a] + djs[s][b]; 로 초기화 한다. 이는 출발지에서 바로 a 와 b 로 가는 거리다. 그 후에 1 - n 까지 반복하며 최단 거리를 계산한다. 즉, 정리하면 밑의 두 경우에 대하여 최소 값을 계산하는 것이다.

 

1. 출발지 s 에서 a 와 b 로 가는 거리

2. s 에서 i 까지 가서 i 부터 a 와 b로 가는 거리

 

코드가 다익스트라에 비하면 굉장히 짧지만 위에서 말한대로 MAX 값 설정에 주의해야 한다. 제한사항에 n 은 3이상 200 이하인 자연수이다. 또한 요금 f 는 100,000 이하인 자연수이다. 최악의 경우, 모든 노드에 대해 요금이 100,000인 경우 이는 20,000,000 이기 때문에 200 * 100,000 + 1 인 20,000,001 로 계산하여, 최대 비용보다 조금 더 큰 값을 설정하여, 실제 가능한 최대 요금과 구별되도록 설정해야 한다. 

 

그에 반해, 나는 무지성으로 987,654,321 설정했다. 이는 최악의 경우 int 의 최대 범위를 넘어 잘못된 값이 최소값이 되어버린다. 이에 주의해서 코드를 작성하자.

✅ 전체코드 (플로이드 워셜)

import java.util.*;

class Solution {
    static final int INF = 20000001;
    
    public int solution(int n, int s, int a, int b, int[][] fares) {
        int[][] adj = new int[n+1][n+1];
        for(int i=1; i<=n; i++) {
                Arrays.fill(adj[i], INF);
                adj[i][i] = 0;
        }

        for(int[] f : fares)
            adj[f[0]][f[1]] = adj[f[1]][f[0]] = f[2];

        // 플로이드 워셜
        for(int i=1; i<=n; i++) {
            for(int j=1; j<=n; j++) {
                for(int k=1; k<=n; k++) {
                        adj[j][k] = Math.min(adj[j][k], adj[j][i] + adj[i][k]);
                }
            }
        }

        int min = adj[s][a] + adj[s][b];
        for(int i=1; i<=n; i++) {
            min = Math.min(min, adj[s][i] + adj[i][a] + adj[i][b]);
        }

        return min;
    }
}

 

2️⃣ 다익스트라

다익스트라를 이용한 풀이는 출발지 s 에서 모든 노드에 대한 최소 거리를 계산하고 이에 대한 비교로 이루어졌다. 바로 코드를 통해 설명하겠다.

 

public int[] dijkstra(int start) {
    boolean[] visited = new boolean[N];
    int[] distance = new int[N];
    Arrays.fill(distance, MAX);
    distance[start] = 0;

    // 최소 거리를 계산해야 하기 때문에 거리 오름차순으로 정렬
    PriorityQueue<int[]> pq = new PriorityQueue<>(Comparator.comparingInt(a -> a[0]));
    pq.add(new int[] {0, start});

    while (!pq.isEmpty()) {
        int[] node = pq.poll();
        int cur = node[1];
        if (visited[cur])
            continue;

        visited[cur] = true;

        for (int next = 0; next < N; next++) {
            // 갈 수 없는 곳 예외처리
            if(adj[cur][next] == 0) 
                continue;

            // 거리 최소 값 계산
            if (distance[next] > distance[cur] + adj[cur][next]) {
                distance[next] = distance[cur] + adj[cur][next];
                pq.add(new int[]{distance[next], next});
            }
        }
    }

    return distance;
}

 

다익스트라 메서드 디자인은 위와 같다. 여기서 주의할 점은 우선순위 큐이다. 다익스트라 알고리즘에서 우선순위 큐를 사용하는 이유는 가장 짧은 거리를 가진 노드를 우선적으로 처리하기 위해서이다. 이 방법을 통해 불필요한 경로의 탐색을 최소화 할 수 있다. 위와 같은 방법을 통해 start 부터 모든 Node까지의 최소 거리를 계산한 int[] 배열을 반환하게 했다.

 

static int solution(int n, int s, int a, int b, int[][] fares) {
    N = n;
    E = fares.length;
    adj = new int[n+1][n+1];

    for(int[] f : fares)
        adj[f[0]][f[1]] = adj[f[1]][f[0]] = f[2];

    // int[], 출발지로부터 최소 거리를 계산
    int[] together = dijkstra(s);

    int minCost = MAX;
    for(int i = 1; i <= N; i++) {
        // i 부터 모든 Node 까지 거리 최소 값 계산
        int[] alone = dijkstra(i);

        // 출발지 -> i + i -> a + i -> b
        int cost = together[i] + alone[a] + alone[b];
        if(cost < minCost) {
            minCost = cost;
        }
    }

    return minCost;
}

 

int[] together 에 출발지로부터 모든 Node까지의 최소 거리를 계산한다. 그 후, 1부터 N까지 노드를 기준으로 

출발지 s 부터 i + i 부터 a + i 부터 b 까지의 거리의 최소를 구해서 그 값을 리턴한다.

 

✅ 전체코드 (다익스트라)

import java.util.*;

class Solution {
    static int N;
    static int E;
    static int[][] adj;
    static final int MAX = Integer.MAX_VALUE;
    
    public int solution(int n, int s, int a, int b, int[][] fares) {
        N = n;
        E = fares.length;
        adj = new int[n+1][n+1];

        for(int[] f : fares)
            adj[f[0]][f[1]] = adj[f[1]][f[0]] = f[2];

        // int[], 출발지로부터 최소 거리를 계산
        int[] together = dijkstra(s);

        int minCost = MAX;
        for(int i = 1; i <= N; i++) {
            // i 부터 모든 Node 까지 거리 최소 값 계산
            int[] alone = dijkstra(i);

            // 출발지 -> i + i -> a + i -> b
            int cost = together[i] + alone[a] + alone[b];
            if(cost < minCost) {
                minCost = cost;
            }
        }

        return minCost;
    }
    
    public int[] dijkstra(int start) {
        boolean[] visited = new boolean[N];
        int[] distance = new int[N];
        Arrays.fill(distance, MAX);
        distance[start] = 0;

        // 최소 거리를 계산해야 하기 때문에 거리 오름차순으로 정렬
        PriorityQueue<int[]> pq = new PriorityQueue<>(Comparator.comparingInt(a -> a[0]));
        pq.add(new int[] {0, start});

        while (!pq.isEmpty()) {
            int[] node = pq.poll();
            int cur = node[1];
            if (visited[cur])
                continue;

            visited[cur] = true;

            for (int next = 0; next < N; next++) {
                // 갈 수 없는 곳 예외처리
                if(adj[cur][next] == 0) 
                    continue;

                // 거리 최소 값 계산
                if (distance[next] > distance[cur] + adj[cur][next]) {
                    distance[next] = distance[cur] + adj[cur][next];
                    pq.add(new int[]{distance[next], next});
                }
            }
        }

        return distance;
    }
}
728x90
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.