coding test

[파이썬] 순위

잔망루피 2021. 4. 18. 21:30

문제 설명

n명의 권투선수가 권투 대회에 참여했고 각각 1번부터 n번까지 번호를 받았습니다. 권투 경기는 1대1 방식으로 진행이 되고, 만약 A 선수가 B 선수보다 실력이 좋다면 A 선수는 B 선수를 항상 이깁니다. 심판은 주어진 경기 결과를 가지고 선수들의 순위를 매기려 합니다. 하지만 몇몇 경기 결과를 분실하여 정확하게 순위를 매길 수 없습니다.

선수의 수 n, 경기 결과를 담은 2차원 배열 results가 매개변수로 주어질 때 정확하게 순위를 매길 수 있는 선수의 수를 return 하도록 solution 함수를 작성해주세요.

 

제한사항

  • 선수의 수는 1명 이상 100명 이하입니다.
  • 경기 결과는 1개 이상 4,500개 이하입니다.
  • results 배열 각 행 [A, B]는 A 선수가 B 선수를 이겼다는 의미입니다.
  • 모든 경기 결과에는 모순이 없습니다.

 

입출력 예

n results return
5 [[4, 3], [4, 2], [3, 2], [1, 2], [2, 5]] 2

 

입출력 예 설명

2번 선수는 [1, 3, 4] 선수에게 패배했고 5번 선수에게 승리했기 때문에 4위입니다.
5번 선수는 4위인 2번 선수에게 패배했기 때문에 5위입니다.

출처

 

 

👸 나의 풀이

 

from collections import defaultdict

def solution(n, results):
    dic = defaultdict(list)  # key:이긴 선수, 값:진 선수
    lose=defaultdict(list)  # key : 진 선수, 값 : 이긴 선수
    result={}       # 승부 결과 수

    for r in results:
        dic[r[0]].append(r[1])
        lose[r[1]].append(r[0])

    def dfs(dic, key):
        visited[key] = True
        for loser in dic[key]:
            if not visited[loser]:
                dfs(dic, loser)

    for key in list(dic.keys()):    # 이긴 선수
        visited = [0] * (n + 1)
        dfs(dic, key)
        result[key]=visited.count(True)-1	# 자기 자신은 제외

    for key in list(lose.keys()) :  # 진 선수
        visited=[0]*(n+1)
        dfs(lose, key)
        try :
            result[key]+=visited.count(True)-1
        except :	# key가 없을 때 발생하는 에러 처리
            result[key]=visited.count(True)-1

    return list(result.values()).count(n-1)

 

문제를 이해하면 어렵지 않게 풀 수 있다.

순위를 알려면 총 사람 수 n-1만큼의 결과(이긴 것+진 것)이 있어야함.

key가 이긴 사람과 진 사람인 두 가지 딕셔너리를 만들었다.

dfs로 visited 기록을 한 후 true의 갯수를 센다. 이 갯수는 key가 이긴 사람의 수 또는 진 사람의 수다.

딕셔너리 result에서 총 사람 수n-1의 갯수를 세서 반환한다.

참고로  list(lose.keys())로 써야 RuntimeError: dictionary changed size during iteration가 뜨지 않는다.

 

 

# 실패
from collections import defaultdict

def solution(n, results):   # 선수의 수, 경기 결과
    answer = 0
    dic=defaultdict(list)
    
    for value, key in results : # key: 선수, value : key에게 짐 
        dic[key].append(value)
        
    return answer

 

딕셔너리에 담아놓고 그 다음에 탐색을 어떻게 해야할지 몰라서 ㅠ

 

 

🙋‍♀️ 다른 사람 풀이

 

# https://velog.io/@tjdud0123/%EC%88%9C%EC%9C%84-python
def solution(n, results) :	# 선수의 수, 경기 결과
    chart=[[0]*n for _ in range(n)] # 승패표
    WIN, LOSE=1, -1
    
    for i, j in results :   # 내입장 win = 상대방 lose
        chart[i-1][j-1], chart[j-1][i-1]=WIN, LOSE
        
    for me in range(n) :
        wins=[opp for opp, rst in enumerate(chart[me]) if rst == WIN]
        while wins :
            loser=wins.pop()
            for opp, rst in enumerate(chart[loser]) :
                if rst == WIN and chart[me][opp] == 0 :
                    chart[me][opp], chart[opp][me]=WIN, LOSE
                    wins.append(opp)
    return len(['know' for x in chart if x.count(0) == 1])

 

chart=[[0, 1, 0, 0, 0], [-1, 0, -1, -1, 1], [0, 1, 0, -1, 0], [0, 1, 1, 0, 0], [0, -1, 0, 0, 0]]

위는 테케의 승패표 예시

 

스택을 사용해서 DFS 구현.

1. wins에 'chart[me] 선수가 이긴 상대 선수들 번호'를 저장.

2. wins에서 뽑은 '경기에서 진 선수 loser'가 이긴 상대방을 wins에 담는다. 

me가 이긴 선수 loser가 이긴 선수 opp는 me 또한 이긴 것으로 기록한다.

정리하면 a를 이긴 사람c는 a에게 진 사람b를 이긴다.

a에게 진 사람c는 a를 이긴 사람b에게 진다.

위 과정 반복

0은 자기 자신이다. 0이 한 개면 순위가 명확하다.

 

 

# https://velog.io/@tjdud0123/%EC%88%9C%EC%9C%84-python
from collections import defaultdict

def solution(n, results):
    answer = 0
    wins=defaultdict(set)
    loses=defaultdict(set)
    
    for a, b in results :
        wins[a].add(b)	# a가 b를 이김
        loses[b].add(a)	# b가 a에게 짐
        
    for i in range(1, n+1) :
        for loser in wins[i] :
            loses[loser] |= loses[i]
        for winner in loses[i] :
            wins[winner] |= wins[i]
            
    for i in range(1, n+1) :
        if len(wins[i])+len(loses[i]) == n-1 :
            answer+=1
            
    return answer

 

loses={3:{4}, 2:{1, 3, 4}, 5:{2}}
wins={4:{2, 3}, 3:{2}, 1:{2}, 2:{5}}

위는 테케의 딕셔너리

 

딕셔너리를 사용한 풀이

'i에게 진 선수 loser에게 진 선수들'과 'i가 진 선수들'을 or연산한다.

2가 5를 이겼으니까 2에게 진 선수들 1, 3, 4가 loses[5]에 추가된다.

'i를 이긴 선수 winner'들과 'i가 이긴 선수들'을 or연산한다.

1이 2를 이겼으니까 2가 이긴 선수 5를 wins[1]에 추가한다.

wins와 loses의 총 길이가 본인을 제외한 총 선수 수와 같으면 등수를 명확히 알 수 있다. 

 

 

# https://inspirit941.tistory.com/98
def solution(n, results):
    # wins[key]=key가 이긴 사람들의 집합
    # loses[key]=key가 이기지 못한 사람들의 집합
    wins, loses={}, {}
    for i in range(1, n+1) :
        wins[i], loses[i]=set(), set()
        
    for i in range(1, n+1) :	# 1번부터 시작
        for battle in results :
            if battle[0] == i :     # i의 승리
                wins[i].add(battle[1])
            if battle[1] == i :     # i의 패배
                loses[i].add(battle[0])
        # i를 이긴 사람들 (loses[i]) => i에게 진 사람(wins[i])은 반드시 이긴다
        for winner in loses[i] :
            wins[winner].update(wins[i])
        # i에게 진 사람들(wins[i]) => i를 이긴 사람들(loses[i])에게는 반드시 진다.
        for loser in wins[i] :
            loses[loser].update(loses[i])
            
    cnt=0
    for i in range(1, n+1) :
        if len(wins[i])+len(loses[i]) == n-1 :
            cnt+=1
    return cnt

 

위 알고리즘과 같다.

딕셔너리를 이용한 풀이.

각 선수의 승리와 패배를 기록한다.

add()는 set의 메소드다.

update()는 딕셔너리에 값 갱신

 

 

// https://willbfine.tistory.com/524
import java.util.*;
class Solution {
    public int solution(int n, int[][] results) {
        int answer = 0;
        order=new LinkedList[n];
        reverse=new LinkedList[n];
        
        // 메모리 공간 할당
        for(int i=0; i<order.length; i++){
            order[i]=new LinkedList<>();
            reverse[i]=new LinkedList<>();
        }
        
        // 값 입력
        for(int i=0; i<results.length; i++){
            int win=results[i][0]-1;
            int lose=results[i][1]-1;
            order[lose].add(win);
            reverse[win].add(lose);
        }
        
        // 함수 호출
        for(int i=0; i<n; i++){
            int sum=0;
            count=0;
            visited=new boolean[n];
            explore(i, order);
            sum=count;
            count=0;
            visited=new boolean[n];
            explore(i, reverse);
            sum+=count;
            
            if(sum == n-1) answer++;
        }
        return answer;
    }
    
    boolean[] visited;
    List<Integer>[] order;
    List<Integer>[] reverse;
    int count;
    
    // 재귀호출을 이용한 dfs
    private void explore(int v, List<Integer>[] graph){
        visited[v]=true;
        for(int adj : graph[v]){
            if(!visited[adj]){
                count++;
                explore(adj, graph);
            }
        }
    }
}

 

진 사람, 이긴 사람 리스트를 만들고 dfs로 수를 센다.

order의 인덱스는 진 사람, reverse의 인덱스는 이긴 사람이다. 각각의 값은 이긴 사람, 진 사람이다.

값을 여러 개 넣어야해서 각 인덱스마다 LinkedList로 공간을 할당해줬다. ArrayList 사용해도 됨. 

삽입/삭제 연산은 LinkedList가 ArrayList보다 더 효율적임.

총 사람 수-1이면 순위를 명확히 알 수 있으므로 answer을 증가시킨다.

 

 

 

 문제 출처 💁‍♀️ 프로그래머스

 

 

 

반응형

'coding test' 카테고리의 다른 글

[파이썬, Java] 완주하지 못한 선수  (0) 2021.04.22
[파이썬] 합승 택시 요금  (0) 2021.04.20
[파이썬] 등굣길  (0) 2021.04.18
[파이썬, Java] 입국심사  (0) 2021.04.17
[파이썬] 여행경로  (0) 2021.04.17