728x90
반응형

문제

N*M의 배열에서 (0,0) 에서 (N-1,M-1)까지 가는 최단거리를 구하여라

 

입력예제

첫째줄 N 과 M을 입력받으며, 두번째줄 부터 M개의 숫자들이 N줄 나옴

5 4
10 5 9 5
4 1 1 7
7 5 1 6
3 4 8 6
3 4 3 6

===> 답: 34

풀이 코드

다익스트라를 이용한 풀이 코드입니다. 위 예제를 이력하여 마지막에 나온 dist 배열의 값은 

[10, 15, 24, 28]
[14, 15, 16, 23]
[21, 20, 17, 23]
[24, 24, 25, 29]
[27, 28, 28, 34]

입니다.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.PriorityQueue;
import java.util.StringTokenizer;

public class Main {

    static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    static StringTokenizer st;

    static int n, m;
    static int[][] MAP; 
    // Node 생성
    static class Node implements Comparable <Node> {
        int y;
        int x;
        int cost;
        Node(int y, int x, int cost) {
            this.y= y;
            this.x=x;
            this.cost=cost;
        }
        @Override
        public int compareTo(Node next) {
            if(cost < next.cost)
                return -1;
            if(cost > next.cost)
                return 1;
            return 0;
        }
    }

    static int[] ydir = {0, 0, 1, -1};
    static int[] xdir = {1, -1, 0, 0}; 

    static void dijkstra(int y, int x) {
        // 1. PQ 설정
        PriorityQueue<Node>pq = new PriorityQueue<>();
        // MAP[0][0] = 0, 0 위치로 진입하는 비용 
        pq.add(new Node(y, x, MAP[y][x]));

        // 2. dist 설정
        // dist[] = index : 노드번호 value : 최단거리 
        // dist[][] = [y][x] 좌표의 최단거리 
        int[][] dist = new int[n][m];
        // 초기화
        for(int i = 0; i < n; i++) {
            for(int j = 0; j < m; j++) {
                dist[i][j] = Integer.MAX_VALUE;
            }
        }
        dist[y][x] = MAP[y][x]; 

        // dijkstra
        while(!pq.isEmpty()) {
            Node now = pq.poll();
            // 갈 수 있는 인접한 방향 체크 
            for(int i = 0; i < 4; i++) {
                int ny = now.y + ydir[i];
                int nx = now.x + xdir[i];
                // ** 필수체크 ** 범위체크 
                if(ny < 0 || nx < 0 || ny >= n || nx >= m)
                    continue;
                // 다음 노드까지의 비용
                // 지금 좌표까지 오기 위해 사용한 비용 + 다음 좌표로 진입하기 위한 비용
                int ncost = dist[now.y][now.x] + MAP[ny][nx];
                // 지금까지 기록된 [ny][nx]까지의 최소비용보다 같거나 크면 pass
                if(dist[ny][nx] <= ncost)
                    continue; 
                dist[ny][nx] = ncost;
                pq.add(new Node(ny, nx, ncost));
            }
        }
        System.out.println(dist[n-1][m-1]); // N-1,M-1 의 최단거리 출력
    }

    public static void main(String[] args) throws IOException {
        st = new StringTokenizer(br.readLine());
        n = Integer.parseInt(st.nextToken());
        m = Integer.parseInt(st.nextToken());
        // map init
        MAP = new int[n][m];
        // input
        for(int i = 0; i < n; i++) {
            st = new StringTokenizer(br.readLine());
            for(int j = 0; j < m; j++) {
                MAP[i][j] = Integer.parseInt(st.nextToken());
            }
        }
        dijkstra(0, 0); //(0,0) 부터 시작
    }
}

 

다익스트라의 원리는 지난번 설명한 포스트에 있으니 참고하세요.

2021.06.14 - [Programe Note/Algorithm] - [Java] 다익스트라 알고리즘 - 최단경로

728x90
반응형
728x90
반응형

베스트앨범 - 3Level

문제설명

스트리밍 사이트에서 장르 별로 가장 많이 재생된 노래를 두 개씩 모아 베스트 앨범을 출시하려 합니다. 노래는 고유 번호로 구분하며, 노래를 수록하는 기준은 다음과 같습니다.

  1. 속한 노래가 많이 재생된 장르를 먼저 수록합니다.
  2. 장르 내에서 많이 재생된 노래를 먼저 수록합니다.
  3. 장르 내에서 재생 횟수가 같은 노래 중에서는 고유 번호가 낮은 노래를 먼저 수록합니다.

노래의 장르를 나타내는 문자열 배열 genres와 노래별 재생 횟수를 나타내는 정수 배열 plays가 주어질 때, 베스트 앨범에 들어갈 노래의 고유 번호를 순서대로 return 하도록 solution 함수를 완성하세요.

 

제한사항

  • genres[i]는 고유번호가 i인 노래의 장르입니다.
  • plays[i]는 고유번호가 i인 노래가 재생된 횟수입니다.
  • genres와 plays의 길이는 같으며, 이는 1 이상 10,000 이하입니다.
  • 장르 종류는 100개 미만입니다.
  • 장르에 속한 곡이 하나라면, 하나의 곡만 선택합니다.
  • 모든 장르는 재생된 횟수가 다릅니다.

입출력 예

입출력 예 설명

classic 장르는 1,450회 재생되었으며, classic 노래는 다음과 같습니다.

  • 고유 번호 3: 800회 재생
  • 고유 번호 0: 500회 재생
  • 고유 번호 2: 150회 재생

pop 장르는 3,100회 재생되었으며, pop 노래는 다음과 같습니다.

  • 고유 번호 4: 2,500회 재생
  • 고유 번호 1: 600회 재생

따라서 pop 장르의 [4, 1]번 노래를 먼저, classic 장르의 [3, 0]번 노래를 그다음에 수록합니다.

 

풀이

해당 문제는 HashMap 정렬을 이용하여 풀었는데, 생각보다 처리 시간이 많이 나옵니다. 다른 방법이 있는지 조금 더 생각할 필요가 있을것 같습니다.

저는 일단 각 장르의 재생횟수가 많은 것을 기준으로 정렬을 하였고, 해당 장르의 고유번호, 재생횟수를 가지는 Music이라는 객체를 만들어 제가 정의한 정렬기준으로 정렬하여 PriorityQueue 를 이용하여 풀었습니다. 아무래도 이중 루프를 돌다보니 시간이 걸리는 것 같습니다.

import java.util.*;
import java.util.Map.*;

class Solution {
    
    public static class Music implements Comparable<Music>{
        int music_num;
        int plays_cnt;
        
        Music(int a, int b){
            this.music_num = a;
            this.plays_cnt = b;
        }
        
        @Override
        public int compareTo(Music tar){
            // 많이 재생된 것
            if(tar.plays_cnt > this.plays_cnt) return 1;
            if(tar.plays_cnt < this.plays_cnt) return -1;
            // 재생수가 같다면 고유번호가 낮은것
            if(tar.music_num < this.music_num) return 1;
            if(tar.music_num > this.music_num) return -1;
            return 0;
        }
        
    }
    
    public Integer[] solution(String[] genres, int[] plays) {
        
        int len = genres.length;
        HashMap<String,Integer> map = new HashMap<String,Integer>();
        for(int i=0; i<len; i++){
       
            if(map.containsKey(genres[i])){
                map.put(genres[i], map.get(genres[i]) + plays[i]);
            }else{
                map.put(genres[i], plays[i]);
            }
            
        }
        
        // map을 value 기준으로 정렬한다.
		List<Entry<String, Integer>> list_entries = new ArrayList<Entry<String, Integer>>(map.entrySet());

		// 비교함수 Comparator를 사용하여 내림차순으로 정렬
		Collections.sort(list_entries, new Comparator<Entry<String, Integer>>() {
			// compare로 값을 비교
			public int compare(Entry<String, Integer> obj1, Entry<String, Integer> obj2) {
				// 내림 차순 정렬
				return obj2.getValue().compareTo(obj1.getValue());
			}
		});
        
        
        ArrayList<Integer> alist = new ArrayList<Integer>();
        
        // 내림차순으로 정렬된 내용으로 출력한다.
        for(Entry<String, Integer> entry : list_entries) {
            String key = entry.getKey();
            //System.out.println(key + ":" + map.get(key));
            
            PriorityQueue<Music> pq = new PriorityQueue<Music>();
            for(int i=0; i<len; i++){
                if(key.equals(genres[i])) {
                    pq.add(new Music(i, plays[i]));
                }  
            }
            
            int loop=0;
            while(!pq.isEmpty()){
                if(loop==2) break; // 2개만 필요하기때문에 최대 두번만 돌린다.
                Music now = pq.poll();
                alist.add(now.music_num);
                loop++;
            }
        }
        
        //저장된 ArrayList 변수를 Integer[] 배열로 변환한다.
        Integer[] answer = alist.toArray(new Integer[alist.size()]);
        
        
        return answer;
    }
}

결과

728x90
반응형
728x90
반응형

위장 - 2Level

문제설명

스파이들은 매일 다른 옷을 조합하여 입어 자신을 위장합니다.

예를 들어 스파이가 가진 옷이 아래와 같고 오늘 스파이가 동그란 안경, 긴 코트, 파란색 티셔츠를 입었다면 다음날은 청바지를 추가로 입거나 동그란 안경 대신 검정 선글라스를 착용하거나 해야 합니다.

스파이가 가진 의상들이 담긴 2차원 배열 clothes가 주어질 때 서로 다른 옷의 조합의 수를 return 하도록 solution 함수를 작성해주세요.

 

제한사항

  • clothes의 각 행은 [의상의 이름, 의상의 종류]로 이루어져 있습니다.
  • 스파이가 가진 의상의 수는 1개 이상 30개 이하입니다.
  • 같은 이름을 가진 의상은 존재하지 않습니다.
  • clothes의 모든 원소는 문자열로 이루어져 있습니다.
  • 모든 문자열의 길이는 1 이상 20 이하인 자연수이고 알파벳 소문자 또는 '_' 로만 이루어져 있습니다.
  • 스파이는 하루에 최소 한 개의 의상은 입습니다.

입출력 예

입출력 예 설명

예제 #1
headgear에 해당하는 의상이 yellow_hat, green_turban이고 eyewear에 해당하는 의상이 blue_sunglasses이므로 아래와 같이 5개의 조합이 가능합니다.

1. yellow_hat 2. blue_sunglasses 3. green_turban 4. yellow_hat + blue_sunglasses 5. green_turban + blue_sunglasses

예제 #2
face에 해당하는 의상이 crow_mask, blue_sunglasses, smoky_makeup이므로 아래와 같이 3개의 조합이 가능합니다.

1. crow_mask 2. blue_sunglasses 3. smoky_makeup

 

풀이

이번 문제 해시를 이용해 푸는 문제로, clothes 배열에서 종류인 인덱스 1의 값이 중요합니다. 종류에 몇가지가 들어있는지를 HashMap 변수 map에 넣고 해당 종류가 있는지 없는지 체크합니다. 여기서 없을때 새로 map에 넣어줘야 하는데 이때 1이 아닌 2를 넣어줘야 합니다. 왜냐하면 옷을 입지 않을 경우도 생각을 해야하기 때문입니다. 그래야 경우의 수를 계산하기 편합니다.

import java.util.*;

class Solution {
    public int solution(String[][] clothes) {
        int answer = 1;
        
        HashMap<String, Integer> map = new HashMap<String, Integer>();
        
        for(int i=0; i<clothes.length; i++){
            if(map.containsKey(clothes[i][1])){
                map.put(clothes[i][1], map.get(clothes[i][1])+1);  
            }else{
                // 옷은 1개이지만 입지않는 경우도 있기때문에 입은경우 , 안입은 경우의 두가지 경우가 있어 2로 입력
                map.put(clothes[i][1], 2);   
            }                
        }
              
        for(String key : map.keySet()){
            answer *= map.get(key);
        }
        
        
        return answer-1;
    }
   
}

 

결과

728x90
반응형
728x90
반응형

전화번호 목록 - 2Level

문제설명

전화번호부에 적힌 전화번호 중, 한 번호가 다른 번호의 접두어인 경우가 있는지 확인하려 합니다.
전화번호가 다음과 같을 경우, 구조대 전화번호는 영석이의 전화번호의 접두사입니다.

  • 구조대 : 119
  • 박준영 : 97 674 223
  • 지영석 : 11 9552 4421

전화번호부에 적힌 전화번호를 담은 배열 phone_book 이 solution 함수의 매개변수로 주어질 때, 어떤 번호가 다른 번호의 접두어인 경우가 있으면 false를 그렇지 않으면 true를 return 하도록 solution 함수를 작성해주세요.

 

제한사항

  • phone_book의 길이는 1 이상 1,000,000 이하입니다.
    • 각 전화번호의 길이는 1 이상 20 이하입니다.
    • 같은 전화번호가 중복해서 들어있지 않습니다.

입출력 예

입출력 예 설명

입출력 예 #1
앞에서 설명한 예와 같습니다.

입출력 예 #2
한 번호가 다른 번호의 접두사인 경우가 없으므로, 답은 true입니다.

입출력 예 #3
첫 번째 전화번호, “12”가 두 번째 전화번호 “123”의 접두사입니다. 따라서 답은 false입니다.

 

풀이

처음 문제를 접하고 이 문제가 왜 해시 문제인지 파악하는데 조금 시간이 걸렸습니다. 이중루프로도 풀수 있을것 같아서였습니다. 하지만 그렇게 되면 아마 타임오버가 나오지 않을까 싶어서 좀 곰곰히 생각을 했습니다.

역시나 HashMap의 containsKey 함수를 아느냐 모르냐를 묻는 문제였습니다. 업무를 하다보면 containsKey 함수는 자주 사용되는 함수는 아니지만 이런 알고리즘 문제를 풀때는 종종 등장을 하는것 같습니다.

 

일단 phone_book의 배열을 모두 HashMap인 map에 넣고, phone_book 배열의 원소 String을 처음부터 j번까지 돌아가면서 map에 저장된 key 값이 있는지 확인하고 있다면 바로 false 해주도록 구현해 보았습니다.

import java.util.*;

class Solution {
    public boolean solution(String[] phone_book) {
        boolean answer = true;
        
        HashMap<String,Integer> map = new HashMap<String,Integer>();
        int i=0;
        for(String pb : phone_book){
            map.put(pb, i++);
        }
        
        for(i=0; i<phone_book.length; i++){
            for(int j=0; j<phone_book[i].length(); j++){
                if(map.containsKey(phone_book[i].substring(0,j))){
                    return false;
                }
            }
        }
        
        return answer;
    }
}

결과

728x90
반응형
728x90
반응형

완주하지 못한 선수(1Level)

문제설명

수많은 마라톤 선수들이 마라톤에 참여하였습니다. 단 한 명의 선수를 제외하고는 모든 선수가 마라톤을 완주하였습니다.

마라톤에 참여한 선수들의 이름이 담긴 배열 participant와 완주한 선수들의 이름이 담긴 배열 completion이 주어질 때, 완주하지 못한 선수의 이름을 return 하도록 solution 함수를 작성해주세요.

 

제한사항

  • 마라톤 경기에 참여한 선수의 수는 1명 이상 100,000명 이하입니다.
  • completion의 길이는 participant의 길이보다 1 작습니다.
  • 참가자의 이름은 1개 이상 20개 이하의 알파벳 소문자로 이루어져 있습니다.
  • 참가자 중에는 동명이인이 있을 수 있습니다.

입출력 예

입출력 예 설명

예제 #1
"leo"는 참여자 명단에는 있지만, 완주자 명단에는 없기 때문에 완주하지 못했습니다.

예제 #2
"vinko"는 참여자 명단에는 있지만, 완주자 명단에는 없기 때문에 완주하지 못했습니다.

예제 #3
"mislav"는 참여자 명단에는 두 명이 있지만, 완주자 명단에는 한 명밖에 없기 때문에 한명은 완주하지 못했습니다.

 

풀이

이 문제는 해시를 얼마나 알고있는지에 대한 문제입니다. 해시를 쓰면 간단하게 Key를 통해 Value를 알 수 있습니다.

이 문제에는 함정이 있는데, 그건 바로 "참가자 중에는 동명이인이 있을 수 있습니다." 라는 것입니다. key값은 중복을 허용하지 않기때문에 이부분에 대한 내용을 어떻게 풀까가 중요한 것 같습니다.

import java.util.*;

class Solution {
    public String solution(String[] participant, String[] completion) {
        String answer = "";
        
        HashMap<String, Integer> map = new HashMap<String, Integer>();
        for(int i=0; i<participant.length ; i++){    
            if(map.get(participant[i]) == null){
                map.put(participant[i], 1);
            }else{
                map.put(participant[i], map.get(participant[i])+1);
            }
        }

        for(int i=0; i<completion.length ; i++){   
            if(map.get(completion[i]) != null){
                map.put(completion[i], map.get(completion[i])-1);
            }
        }
        
        for(int i=0; i<participant.length ; i++){  
            if(map.get(participant[i]) != 0){
                answer = participant[i] ;
                break;
            }
        }
        
        
        return answer;
    }
}

결과

728x90
반응형
728x90
반응형

K-번째수

문제설명

배열 array의 i번째 숫자부터 j번째 숫자까지 자르고 정렬했을 때, k번째에 있는 수를 구하려 합니다.

예를 들어 array가 [1, 5, 2, 6, 3, 7, 4], i = 2, j = 5, k = 3이라면

  1. array의 2번째부터 5번째까지 자르면 [5, 2, 6, 3]입니다.
  2. 1에서 나온 배열을 정렬하면 [2, 3, 5, 6]입니다.
  3. 2에서 나온 배열의 3번째 숫자는 5입니다.

배열 array, [i, j, k]를 원소로 가진 2차원 배열 commands가 매개변수로 주어질 때, commands의 모든 원소에 대해 앞서 설명한 연산을 적용했을 때 나온 결과를 배열에 담아 return 하도록 solution 함수를 작성해주세요.

 

제한사항

  • array의 길이는 1 이상 100 이하입니다.
  • array의 각 원소는 1 이상 100 이하입니다.
  • commands의 길이는 1 이상 50 이하입니다.
  • commands의 각 원소는 길이가 3입니다.

입출력 예

입출력 예 설명

[1, 5, 2, 6, 3, 7, 4]를 2번째부터 5번째까지 자른 후 정렬합니다. [2, 3, 5, 6]의 세 번째 숫자는 5입니다.
[1, 5, 2, 6, 3, 7, 4]를 4번째부터 4번째까지 자른 후 정렬합니다. [6]의 첫 번째 숫자는 6입니다.
[1, 5, 2, 6, 3, 7, 4]를 1번째부터 7번째까지 자릅니다. [1, 2, 3, 4, 5, 6, 7]의 세 번째 숫자는 3입니다.

 

풀이

이번 문제는 정렬에 관련된 간단한 Level 1 문제입니다. Array 의 복사, 정렬등에 대한 함수를 알고 있는지 파악하려는 것 같습니다. 기본적인 문제이니 확실히 확인하는 것이 좋을것 같습니다.

import java.util.*;

class Solution {
    public int[] solution(int[] array, int[][] commands) {
        ArrayList<Integer> ret = new ArrayList<Integer>();
        
        for(int n=0; n<commands.length; n++){
            int i = commands[n][0];
            int j = commands[n][1];
            int k = commands[n][2];
            
            int[] tmp = Arrays.copyOfRange(array, i-1, j);
            Arrays.sort(tmp);
            ret.add(tmp[k-1]);
        }
        
        int[] answer = new int[ret.size()];
        for(int i=0; i<ret.size(); i++){
            answer[i] = ret.get(i);
        }

        return answer;
    }
}

결과

728x90
반응형
728x90
반응형

단어변환

문제설명

두 개의 단어 begin, target과 단어의 집합 words가 있습니다. 아래와 같은 규칙을 이용하여 begin에서 target으로 변환하는 가장 짧은 변환 과정을 찾으려고 합니다.

  1. 한 번에 한 개의 알파벳만 바꿀 수 있습니다.
  2. words에 있는 단어로만 변환할 수 있습니다.

예를 들어 begin이 "hit", target가 "cog", words가 ["hot","dot","dog","lot","log","cog"]라면 "hit" -> "hot" -> "dot" -> "dog" -> "cog"와 같이 4단계를 거쳐 변환할 수 있습니다.

두 개의 단어 begin, target과 단어의 집합 words가 매개변수로 주어질 때, 최소 몇 단계의 과정을 거쳐 begin을 target으로 변환할 수 있는지 return 하도록 solution 함수를 작성해주세요.

 

제한사항

  • 각 단어는 알파벳 소문자로만 이루어져 있습니다.
  • 각 단어의 길이는 3 이상 10 이하이며 모든 단어의 길이는 같습니다.
  • words에는 3개 이상 50개 이하의 단어가 있으며 중복되는 단어는 없습니다.
  • begin과 target은 같지 않습니다.
  • 변환할 수 없는 경우에는 0를 return 합니다.

입출력 예

  • 각 단어는 알파벳 소문자로만 이루어져 있습니다.
  • 각 단어의 길이는 3 이상 10 이하이며 모든 단어의 길이는 같습니다.
  • words에는 3개 이상 50개 이하의 단어가 있으며 중복되는 단어는 없습니다.
  • begin과 target은 같지 않습니다.
  • 변환할 수 없는 경우에는 0를 return 합니다.

입출력 예 설명

예제 #1
문제에 나온 예와 같습니다.

예제 #2
target인 "cog"는 words 안에 없기 때문에 변환할 수 없습니다.

 

풀이

해당문제는 전형적인 BFS문제입니다. BFS 문제의 경우 "최단","최소"를 찾는 경우가 많습니다. 문제에서도 단어가 변환되는 최소의 단계를 구하는 것이기 때문에 BFS 알고리즘을 사용하여 풀면 되겠습니다.

BFS 알고리즘의 설명은 아래를 참고하여 주십시오.

https://tylee82.tistory.com/329?category=940295 
 

[JAVA] 너비 우선 탐색 BFS(Breadth-first search)

동작방식 하나로 시작 정점을 방문한 후 시작 정점에 인접한 모든 정점들을 우선 방문하는 방법입니다. 더 이상 방문하지 않은 정점이 없을 때까지 방문하지 않은 모든 정점들에 대해서도 너비

tylee82.tistory.com

 

이외에도 isCanChange 함수에서 문자1(str1)과 문자2(str2)를 비교해 한개의 알파벳만 변환을 했는지 검사를 하는 함수가 있습니다. 저는 str1과 str2의 문자 하나하나를 char로 변환하여 빼기를 하여 절대값을 해주었습니다. 이렇게하면 문자의 차이가 나오는데 같은 문자라면 '0'이 나타날 것 입니다. '0'이 아닌 위치는 알파벳이 변환이 되었으니 이후에 나오는 알파벳은 모두 '0'이 나와야 한다는 가정으로 새우고 함수를 만들었습니다.

import java.util.*;

class Solution {
    
    class Word{
        String str;
        int count;
        
        Word(String str, int count){
            this.str = str;
            this.count = count;
        }
    }
    
    
    public boolean isCanChange(String str1, String str2){
    	//한 번에 한 개의 알파벳만 바꿀 수 있습니다.
        boolean ret = false;
        
        int len = str1.length();
        
        boolean isOneChange = false;
        int diffWord = 0;
        for(int i=0; i<len; i++){
            diffWord = Math.abs(str1.charAt(i) - str2.charAt(i));
            
            if(!isOneChange && diffWord != 0) {
            	isOneChange = true;
            	ret = true;
            }else if(isOneChange && diffWord != 0){
            	ret = false;
            	break;
            }
        }
        
        return ret;
    }

    
    public int solution(String begin, String target, String[] words) {
        int answer = 0;
        
        int[] used = new int[words.length]; // 사용한것은 1, 안한것은 0
        Queue<Word> q = new LinkedList<Word>();
        q.add(new Word(begin, 0)); //시작점
        
        while(!q.isEmpty()){
            
            Word cur = q.poll();
            
            if(cur.str.equals(target)){
                // terget과 문자가 동일하면 cur.count 반환하고 멈춤
                answer = cur.count;
                break;
            }
            
            for(int i=0; i<words.length; i++){
                if(isCanChange(cur.str, words[i] ) && used[i] == 0) //변환이 가능하고 사용하지 않았으면 q에 넣어준다.
                {
                    used[i] = 1; // 방문 표시
                    q.add(new Word(words[i] , cur.count+1));
                }
            }
            
        }
        
        
        return answer;
    }
}

결과

 

728x90
반응형
728x90
반응형

네트워크 문제

문제설명

네트워크란 컴퓨터 상호 간에 정보를 교환할 수 있도록 연결된 형태를 의미합니다. 예를 들어, 컴퓨터 A와 컴퓨터 B가 직접적으로 연결되어있고, 컴퓨터 B와 컴퓨터 C가 직접적으로 연결되어 있을 때 컴퓨터 A와 컴퓨터 C도 간접적으로 연결되어 정보를 교환할 수 있습니다. 따라서 컴퓨터 A, B, C는 모두 같은 네트워크 상에 있다고 할 수 있습니다.

컴퓨터의 개수 n, 연결에 대한 정보가 담긴 2차원 배열 computers가 매개변수로 주어질 때, 네트워크의 개수를 return 하도록 solution 함수를 작성하시오.

 

제한사항

  • 컴퓨터의 개수 n은 1 이상 200 이하인 자연수입니다.
  • 각 컴퓨터는 0부터 n-1인 정수로 표현합니다.
  • i번 컴퓨터와 j번 컴퓨터가 연결되어 있으면 computers[i][j]를 1로 표현합니다.
  • computer[i][i]는 항상 1입니다.

입출력 예

입출력 예 설명

예제 #1

아래와 같이 2개의 네트워크가 있습니다.

예제 #2

아래와 같이 1개의 네트워크가 있습니다.

 

풀이방법

프로그래머스에서 제공하는 코딩테스트 연습문제 Level 3 의 문제입니다. 일단 네트워크 문제는 분류가 깊이/너비 우선 탐색(DFS/BFS)로 분류가 되어있습니다. 하지만 저는 이 문제를 Union-Find로 풀어보려 합니다. 왜냐하면 연결된 그룹을 찾는 문제이고 주어진 입력값으로 각 컴퓨터를 연결하여 집합으로 묶어주면 그 집합의 개수만 카운트 해주면 될 것 같아서 저는 Union-Find 로 풀어보았습니다.

https://tylee82.tistory.com/291
 

[Java] Union-Find (유니온-파인드) 알고리즘

Union-Find는 자료 구조입니다. 그래프 알고리즘에서 사용하는 대표 알고리즘입니다. 여러개의 노드가 존재할 때 두 개의 노드를 선택해서, 현재 이 두 노드가 서로 같은 그래프에 속하는지 판별하

tylee82.tistory.com

import java.util.*;

class Solution {
    
    static int[] uArr;
    
    public int solution(int n, int[][] computers) {
        int answer = 0;
        uArr = new int[n+4];
        
        for(int i=0; i<n ; i++){
            for(int j=0; j<n ; j++){
                if(i==j) continue; // computer[i][i]는 항상 1 이기때문에 필요없음
                if(computers[i][j]==1){ // 연결된 컴퓨터끼리 Union 해줌
                    setUnion(i, j); 
                }
            }
        }
        
        // System.out.println("0:"+getFind(0));
        // System.out.println("1:"+getFind(1));
        // System.out.println("2:"+getFind(2));
        
        // 집합의 개수를 카운트하기위한 변수
        ArrayList<Integer> alist = new ArrayList<Integer>();
        for(int i=0; i<n ; i++){
            if( alist.contains(getFind(i)) ){
                // getFind로 찾은 집합의 우두머리가 이미 alist 변수에 있으면 pass
                continue;  
            } 
            else {
                // alist 에 없으면 추가
                alist.add(getFind(i));
            }
        }
        
        // 집합은 alist 의 size 이다.
        answer = alist.size();
        
        return answer;
    }
    
    public void setUnion(int i, int j){
        int a = getFind(i);
        int b = getFind(j);
        if(a == b) return;
        uArr[a] = b;
    }
    
    public int getFind(int tar){
        if(uArr[tar] == 0) return tar;
        int ret = getFind(uArr[tar]);
        uArr[tar] = ret;
        return ret;
    }
}

 

결과

728x90
반응형
728x90
반응형

신장트리(Spanning Tree)는 그래프 내의 모든 정점을 포함하는 트리입니다. MST(Minimum Spanning Tree)는 신장트리 중에서 사용된 간선들의 가중치 합이 최소인 트리를 이야기합니다. MST는 크루스칼 그리고 프림 알고리즘이 있습니다. 여기서는 크루스칼 알고리즘을 다루겠습니다.

 

  • 그래프 내의 간선의 숫자가 적은 희소 그래프의 경우 : 크루스칼 알고리즘
  • 그래프 내의 간선의 숫자가 많은 밀집 그래프의 경우 : 프림 알고리즘

동작방식

탐욕적 방법(Greedy)을 이용하여 가중치 그래프의 모든 정점을 최소 비용으로 연결하는 방법입니다.

  1. 그래프의 간선들을 가중치의 오름차순으로 정렬합니다.
  2. 정렬된 간선 리스트에서 순서대로 사이클을 형성하지 않는 간선을 선택합니다.
    • 즉, 가장 낮은 가중치를 먼저 선택합니다.
    • 사이클을 형성하는 간선을 제외합니다.
    • 해당 간선을 현재의 MST의 집합에 추가합니다.

시간복잡도

Union-Find 알고리즘을 이용하면 크루스칼 알고리즘의 시간 복접도는 간선들을 정렬하는 시간에 좌우됩니다.

간선 N개를 효율적으로 정렬한다면 O(N logN)의 시간복잡도를 갖는다.

참고사이트

https://hibee.tistory.com/300
 

[AL] 7. 최소 비용 신장 트리(MST) 알고리즘

7. 최소 비용 신장 트리(MST) 알고리즘 신장 트리 (Spanning Tree) 최소 비용 신장 트리 (MST) Kruskal MST 알고리즘 Prim MST 알고리즘 1. 신장 트리 (Spanning Tree) 1.1. Spanning Tree 그래프..

hibee.tistory.com

기본코드

입력값

7
11
1 2 2
2 7 7
7 6 9
6 5 23
5 4 1
4 1 10
1 3 3
2 3 3
3 7 4
3 6 3
3 5 6

코드

package study.graph;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.PriorityQueue;
import java.util.StringTokenizer;

class A implements Comparable<A>{
	
	int a;
	int b;
	int dist;
	
	A (int a, int b, int dist){
		this.a = a;
		this.b = b;
		this.dist = dist;
	}


	@Override
	public int compareTo(A tar) {
		if(tar.dist < this.dist) return 1;
		if(tar.dist > this.dist) return -1;
		return 0;
	}
	
}

public class MST_크루스칼 {

	static int[] uArr = null;
	
	public static int getFind(int tar) {
		
		if(uArr[tar] == 0) return tar;
		int ret = getFind(uArr[tar]);
		uArr[tar] = ret;
		return ret;
	}
	
	public static void setUnion(int t1, int t2) {
		int a = getFind(t1);
		int b = getFind(t2);
		if(a == b) return;
		uArr[b] = a;
	}
	
	public static void main(String arg[]) throws IOException{
		
		System.setIn(new FileInputStream("input\\kruskal.txt"));
		
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
		
		int N = Integer.parseInt(br.readLine()); // 정점의 개수
		int M = Integer.parseInt(br.readLine()); // 간선의 개수

		PriorityQueue<A> pq = new PriorityQueue<A>();
		
		StringTokenizer st = null; 
		for(int j=0; j<M; j++) {
			st = new StringTokenizer(br.readLine());
			int a = Integer.parseInt(st.nextToken());
			int b = Integer.parseInt(st.nextToken());
			int dist = Integer.parseInt(st.nextToken());
			
			pq.add(new A(a, b, dist)); // 큐에 모든 간선을 넣는다.
			pq.add(new A(b, a, dist));
		}
		
		uArr = new int[N+1];
		
		int sumDist = 0;
		while(!pq.isEmpty()) {
			A tar = pq.poll();
			
			int start = tar.a;
			int end = tar.b;
			int a = getFind(start); // start 의 부모(root) 확인
			int b = getFind(end); // end 의 부모(root) 확인
			
			if(a == b) continue; // 부모가 같다면 사이클이 형성되어 있다고 판단하여 다음으로 진행
			
			setUnion(start, end); // 두개의 부모가 같지 않기 때문에 간선으로 연결가능, 그렇다면 유니온하여 부모로 설정
			sumDist += tar.dist; // 간선의 거리를 더한다.
		}
		
		bw.write("# " + sumDist + "\n");
		
		br.close();
		bw.close();
		
	}
	
}
728x90
반응형
728x90
반응형

 

신장트리(Spanning Tree)는 그래프 내의 모든 정점을 포함하는 트리입니다. MST(Minimum Spanning Tree)는 신장트리 중에서 사용된 간선들의 가중치 합이 최소인 트리를 이야기합니다. MST는 크루스칼 그리고 프림 알고리즘이 있습니다. 여기서는 프림알고리즘을 다루겠습니다.

 

  • 그래프 내의 간선의 숫자가 적은 희소 그래프의 경우 : 크루스칼 알고리즘
  • 그래프 내의 간선의 숫자가 많은 밀집 그래프의 경우 : 프림 알고리즘

 

동작방식

시작 정점에서부터 출발하여 신장트리 집합을 단계적으로 확장해 나가는 방법

  1. 시작 단계에서는 시작 정점만이 MST 에 포함합니다.
  2. 앞 단계에서 만들어진 MST 집합에 인접한 정점들 중에서 최소 간선으로 연결된 정점을 선택하여 트리를 확장합니다.
    • 즉, 가장 낮은 가중치를 먼저 선택합니다.
  3. 위의 과정을 트리가 N-1 개의 간선을 가질 때까지 반복합니다.

시간복잡도

기본적으로 O(N^2) 의 시간복잡도를 가지고 있습니다. 우선순위 큐를 사용한다면 O( E logV)의 시간복잡도를 가집니다.

참고사이트

https://hibee.tistory.com/300
 

[AL] 7. 최소 비용 신장 트리(MST) 알고리즘

7. 최소 비용 신장 트리(MST) 알고리즘 신장 트리 (Spanning Tree) 최소 비용 신장 트리 (MST) Kruskal MST 알고리즘 Prim MST 알고리즘 1. 신장 트리 (Spanning Tree) 1.1. Spanning Tree 그래프..

hibee.tistory.com

기본코드

7
11
1 2 2
2 3 5
1 3 20
1 4 10
4 5 1
5 6 23
3 6 3
3 5 6
7 6 9
7 3 2
2 7 7
package study.graph;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.StringTokenizer;

class B implements Comparable<B>{
	int n;
	int dist;
	
	B (int a, int b){
		this.n = a;
		this.dist = b;
	}

	@Override
	public int compareTo(B tar) {
		if(tar.dist < this.dist) return 1;
		if(tar.dist > this.dist) return -1;
		return 0;
	}
	
	
}

public class MST_프림 {

	public static void main(String[] args) throws IOException {
		
		System.setIn(new FileInputStream("input\\prim.txt"));
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
		
		int N = Integer.parseInt(br.readLine());
		int M = Integer.parseInt(br.readLine());
		
		int[] used = new int[N+1]; // 방문 여부 (0: 미방문, 1: 방문)
		
		ArrayList<ArrayList<B>> alist = new ArrayList<ArrayList<B>>();
		for(int i=0; i<=N; i++) {
			alist.add(new ArrayList<B>());
		}
		
		StringTokenizer st = null;
		for(int m=1; m<=M; m++) {
			st = new StringTokenizer(br.readLine());
			int a = Integer.parseInt(st.nextToken());
			int b = Integer.parseInt(st.nextToken());
			int dist = Integer.parseInt(st.nextToken());
			
			alist.get(a).add(new B(b, dist));
			alist.get(b).add(new B(a, dist));
		}
		
		PriorityQueue<B> pq = new PriorityQueue<B>();
		Queue<Integer> q = new LinkedList<Integer>();
		q.add(1); // 임의의 한 노드를 지정
		int sumDist = 0;
		
		while(!q.isEmpty()) {
			int currentNode = q.poll();
			used[currentNode] = 1; // 방문표시
			
			// 현재 노드에 연결된 간선을 pq에 넣음(방문한 노드는 제외)
			for(int i=0; i<alist.get(currentNode).size(); i++) {
				if(used[alist.get(currentNode).get(i).n] != 1) {
					pq.add(alist.get(currentNode).get(i));
				}
			}
			
			while(!pq.isEmpty()) {
				B tar = pq.poll();
				
				if(used[tar.n] == 1) continue; // 방문한 노드면 pass
				
				used[tar.n]= 1;// 방문표시
				sumDist += tar.dist;
				q.add(tar.n); // 연결된 노드를 큐에 넣어줌
				break; // pq에서 방문하지 않은 노드로 가는 가장 dist가 작은 것을 석택해주었으니 여기서 멈춤
			}
		}
		
		bw.write("# " + sumDist);
		
		bw.close();
		br.close();
		
	}
}
728x90
반응형

+ Recent posts