순간을 기록으로

[Java] 프로그래머스_완주하지 못한 선수_해시 본문

Problem Solving

[Java] 프로그래머스_완주하지 못한 선수_해시

luminous13 2022. 6. 22. 17:30

안녕하세요 루미너스입니다! 오늘은 프로그래머스 고득점 Kit 해시 문제를 풀어봤습니다.

 

문제

https://programmers.co.kr/learn/courses/30/lessons/42576#

 

코딩테스트 연습 - 완주하지 못한 선수

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

programmers.co.kr

첫 번째 풀이 - List 이용, 시간초과

import java.util.*;

class Solution {
    public String solution(String[] participant, String[] completion) {
        // 참가자 명단을 ArrayList에 담습니다.
        List<String> list = new LinkedList<>();
        for (String participantName : participant)
            list.add(participantName);
        
        // 완주자 명단을 ArrayList에서 뺍니다.
        for (String completionName : completion)
            list.remove(completionName);
        
        // 첫 번째 요소를 반환합니다.
        return list.get(0);
    }
}

답은 맞았지만 시간초과가 나왔다.

왜 시간초과나 나왔을까? ArrayList의 add, remove 메소드의 시간복잡도를 찾아봤다.

찾아보니 ArrayList - add는 O(1)을 갖지만, remove의 경우 O(n)을 갖기 때문에 for문과 합쳐 O(n^2) 시간복잡도를 갖는다.

입력 데이터가 최대 100,000까지 올 수 있으니 100,000^2 = 100억이게 되고 1억 연산횟수일 때 1초이니 100초가 걸려 시간초과가 나게 된다.

 

LinkedList의 경우 add()는 O(1)이고 remove도 O(1)이다. 다만 remove()의경우 삭제할 노드의 참조를 알 경우 O(1)이다. 만약 순회하면서 삭제할 노드를 찾는다면 remove의 시간복잡도는 O(n)이 되어 마찬가지로 총 시간복잡도는 O(n^2)될 것이다.

 

두 번째 풀이 - HashMap , 성공

 

import java.util.*;
class Solution {
    public String solution(String[] participant, String[] completion) {
        HashMap<String, Integer> map = new HashMap<>();
        
        // 참가자 목록을 해시맵에 등록합니다. ket:이름 value:명
        for (String participantName : participant)
            map.put(participantName, map.getOrDefault(participantName,0) + 1);
        
        // 완주자 목록을 해시맵에서 뺍니다.
        for (String completionName : completion) {
            // 만약 value가 0이라면 엔트리를 삭제합니다.
            map.put(completionName, map.get(completionName) -1);
            if (map.get(completionName) == 0)
                map.remove(completionName);
        }
        
        Iterator it = map.keySet().iterator();
    
        return (String)it.next();
    }
}

다른 사람이 푼 코드

import java.util.HashMap;

class Solution {
    public String solution(String[] participant, String[] completion) {
        String answer = "";
        HashMap<String, Integer> hm = new HashMap<>();
        for (String player : participant) hm.put(player, hm.getOrDefault(player, 0) + 1);
        for (String player : completion) hm.put(player, hm.get(player) - 1);

        for (String key : hm.keySet()) {
            if (hm.get(key) != 0){
                answer = key;
            }
        }
        return answer;
    }
}

나는 값을 빼는 과정에서 0이되면 엔트리를 삭제하는 방식을 취했다. 반면 다른 풀이를 보면 더하는 과정과 빼는 과정을 끝낸 뒤에 key 집합을 가지고 value가 0이 아닌 key를 찾고 그 key를 반환하는 방식을 했다. 어떻게 보면 나는 2,3번째 과정이 엮여있었지만 다른 풀이를 보면 모두 역할에 따른 구현이 분리되어 가독성이 있어보인다. 좋은 알고리즘인거 같다.

 

느낀점

각 컬렉션 클래스들 메소드마다 시간복잡도가 어떻게 되는알 수 있었던 문제였다.

  add() remove() get() contains()
ArrayList O(1) O(n) O(1) O(n)
LinkedList O(1) O(1) O(n) O(n)

참고로 LinkedList의 삭제 연산 remove()는 삭제할 노드에 대한 참조를 가지고 있다는 가정하에 O(1)이다. 만약 순회하면서 지워야할 데이터를 찾아야된다면 remove의 시간복잡도는 O(n)이 된다.

참고

http://infotechgems.blogspot.com/2011/11/java-collections-performance-time.html

 

Java Collections – Performance (Time Complexity)

Many developers I came across in my career as a software developer are only familiar with the most basic data structures, typically, Array,...

infotechgems.blogspot.com

 

Comments