관리 메뉴

솜씨좋은장씨

[Programmers] 달리기 경주 (Python) (feat.ChatGPT) 본문

Programming/코딩 1일 1문제

[Programmers] 달리기 경주 (Python) (feat.ChatGPT)

솜씨좋은장씨 2023. 4. 23. 13:29
728x90
반응형

코딩 1일 1문제! 오늘의 문제는 프로그래머스의 달리기 경주 입니다.

 

프로그래머스

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

programmers.co.kr

👨🏻‍💻 문제 풀이 - SOMJANG

이 문제는 주어지는 입력의 크기가 커서 신경써서 코드를 풀지 못하면 시간초과를 받을 수 있는 문제입니다.

def solution(players, callings):    
    for call in callings:
        idx = players.index(call)
        
        players[idx], players[idx-1] = players[idx-1], players[idx]
    
    return players

실제로 첫 풀이에서 .index 를 활용하여 문제를 풀어보니 시간 초과가 발생했습니다.

이에 .index 를 사용하지 않고도 문제를 풀 수 있도록 해보았습니다.

1. 먼저 선수:랭크 / 랭크:선수 형태의 dictionary 2개를 만들어줍니다.

2. 그 다음 심판이 불렀던 선수 이름을 하나씩 꺼내오면서

👨🏻‍💻 코드 ( Solution ) - SOMJANG

def solution(players, callings):    
    player_dict = {player:rank for rank, player in enumerate(players)}
    rank_dict = {rank:player for rank, player in enumerate(players)}
    
    for call in callings:
        rank = player_dict[call]
        
        player_dict[rank_dict[rank-1]], player_dict[rank_dict[rank]] = player_dict[rank_dict[rank]], player_dict[rank_dict[rank-1]]
        rank_dict[rank-1], rank_dict[rank] = rank_dict[rank], rank_dict[rank-1]
    
    return list(rank_dict.values())

👨🏻‍💻 ChatGPT 부탁해!

👨🏻‍💻 SOMJANG

당신은 Python3 언어로 프로그래밍을 할 수 있는 개발자 입니다.

문제가 주어지면 문제의 맥락을 파악하고 파악한 내용을 Python3 언어를 사용하여 코드로 작성합니다.

문제를 풀때는 코드의 성능을 고려하여 풀어주세요. (-> 이번에는 성능을 고려해달라는 말도 추가해보았습니다.)

작성한 코드에서 설명이 필요한 부분에는 주석을 달아 설명해줍니다.

이제 문제를 드리겠습니다.

 

문제 설명

얀에서는 매년 달리기 경주가 열립니다. 해설진들은 선수들이 자기 바로 앞의 선수를 추월할 때 추월한 선수의 이름을 부릅니다. 예를 들어 1등부터 3등까지 "mumu", "soe", "poe" 선수들이 순서대로 달리고 있을 때, 해설진이 "soe"선수를 불렀다면 2등인 "soe" 선수가 1등인 "mumu" 선수를 추월했다는 것입니다. 즉 "soe" 선수가 1등, "mumu" 선수가 2등으로 바뀝니다. 선수들의 이름이 1등부터 현재 등수 순서대로 담긴 문자열 배열 players와 해설진이 부른 이름을 담은 문자열 배열 callings가 매개변수로 주어질 때, 경주가 끝났을 때 선수들의 이름을 1등부터 등수 순서대로 배열에 담아 return 하는 solution 함수를 완성해주세요.

 

제한사항

5 ≤ players의 길이 ≤ 50,000

players[i]는 i번째 선수의 이름을 의미합니다.

players의 원소들은 알파벳 소문자로만 이루어져 있습니다.

players에는 중복된 값이 들어가 있지 않습니다.

3 ≤ players[i]의 길이 ≤ 10

2 ≤ callings의 길이 ≤ 1,000,000

callings는 players의 원소들로만 이루어져 있습니다.

경주 진행중 1등인 선수의 이름은 불리지 않습니다.

 

입출력 예

players callings result

["mumu", "soe", "poe", "kai", "mine"] ["kai", "kai", "mine", "mine"] ["mumu", "kai", "mine", "soe", "poe"]

 

입출력 예 설명

입출력 예 #1

4등인 "kai" 선수가 2번 추월하여 2등이 되고 앞서 3등, 2등인 "poe", "soe" 선수는 4등, 3등이 됩니다. 5등인 "mine" 선수가 2번 추월하여 4등, 3등인 "poe", "soe" 선수가 5등, 4등이 되고 경주가 끝납니다. 1등부터 배열에 담으면 ["mumu", "kai", "mine", "soe", "poe"]이 됩니다.

💻 ChatGPT - 실패! ( 성능도 정답도 모두 실패! )

해결 방법:

이 문제는 경주가 진행될 때마다 선수들의 등수가 변경되므로, 각 선수의 현재 등수를 계속 추적해야 합니다. 이를 위해 딕셔너리를 사용하여 각 선수의 이름과 현재 등수를 저장합니다. 또한, 해설진이 부른 선수를 확인하면서, 해당 선수가 추월한 선수들의 등수를 적절히 조정해줍니다. 모든 경주가 끝나면 딕셔너리를 등수순으로 정렬하고, 각 선수의 이름을 배열에 담아 반환합니다.

  1. 각 선수의 이름과 등수를 저장하는 딕셔너리를 생성합니다.
  2. players 배열을 반복하면서 딕셔너리에 각 선수의 이름과 현재 등수를 저장합니다.
  3. callings 배열을 반복하면서, 해당 선수가 추월한 선수들의 등수를 적절히 조정합니다.
  4. 모든 경주가 끝난 후, 딕셔너리를 등수순으로 정렬합니다.
  5. 각 선수의 이름을 배열에 담아 반환합니다.

코드 예시:

def solution(players, callings):
    # 각 선수의 이름과 등수를 저장하는 딕셔너리 생성
    rank_dict = {player: idx+1 for idx, player in enumerate(players)}
    # 경주가 끝난 후 선수들의 이름을 담을 배열
    result = players[:]
    # 해설진이 부른 선수를 확인하면서 등수를 조정
    current_rank = len(players)
    for calling in callings:
        # 해당 선수가 추월한 선수들의 등수를 1씩 감소시킴
        for player, rank in rank_dict.items():
            if rank < rank_dict[calling]:
                rank_dict[player] += 1
        # 해당 선수의 등수를 갱신함
        rank_dict[calling] = 1
    # 딕셔너리를 등수순으로 정렬하고, 각 선수의 이름을 배열에 담아 반환
    sorted_players = sorted(rank_dict.items(), key=lambda x: x[1])
    result = [player[0] for player in sorted_players]
    return result

이 코드의 시간 복잡도는 O(nlogn)입니다. 이는 딕셔너리를 등수순으로 정렬하는 과정에서 발생하는 정렬 알고리즘의 시간 복잡도 때문입니다. 만약 정렬 알고리즘이 퀵소트와 같은 O(nlogn)의 시간 복잡도를 가지는 경우에도, 이 코드의 최악 시간 복잡도는 O(n^2)이 될 수 있습니다.

👨🏻‍💻 SOMJANG

제한사항을 보면 1등이 바뀌는 경우가 없는데 현재 이 풀이에서는 1등이 바뀝니다.

제한사항

5 ≤ players의 길이 ≤ 50,000

players[i]는 i번째 선수의 이름을 의미합니다.

players의 원소들은 알파벳 소문자로만 이루어져 있습니다.

players에는 중복된 값이 들어가 있지 않습니다.

3 ≤ players[i]의 길이 ≤ 10

2 ≤ callings의 길이 ≤ 1,000,000 callings는 players의 원소들로만 이루어져 있습니다.

경주 진행중 1등인 선수의 이름은 불리지 않습니다.

 

입출력 예

players callings result

["mumu", "soe", "poe", "kai", "mine"] ["kai", "kai", "mine", "mine"] ["mumu", "kai", "mine", "soe", "poe"]

 

입출력 예 설명

입출력 예 #1

4등인 "kai" 선수가 2번 추월하여 2등이 되고 앞서 3등, 2등인 "poe", "soe" 선수는 4등, 3등이 됩니다. 5등인 "mine" 선수가 2번 추월하여 4등, 3등인 "poe", "soe" 선수가 5등, 4등이 되고 경주가 끝납니다. 1등부터 배열에 담으면 ["mumu", "kai", "mine", "soe", "poe"]이 됩니다.

 

제한사항과 예시를 잘 보고 다시 풀어주세요.

💻 ChatGPT - 실패! ( 에러 발생! )

네, 죄송합니다. 제가 문제의 제한사항과 예시를 더 자세히 확인해보니, 1등인 선수의 이름은 callings 배열에 포함되지 않는다는 것을 알아냈습니다. 따라서, 제가 이전에 작성한 코드는 잘못된 결과를 출력하였습니다.

 

다시 한번 제한사항과 예시를 고려하여 새로운 코드를 작성해보겠습니다. 감사합니다.

def solution(players, callings):
    # 선수 이름과 초기 순위를 dictionary로 저장
    ranks = {player: i+1 for i, player in enumerate(players)}
    # 현재 순위를 저장하는 리스트 생성
    current_ranks = [ranks[player] for player in players]
    
    # callings을 하나씩 처리하면서 순위 업데이트
    for calling in callings:
        # 호출한 선수의 현재 순위
        calling_rank = current_ranks[ranks[calling]-1]
        # 호출당한 선수의 현재 순위
        called_rank = current_ranks[ranks[calling]]
        
        # 호출한 선수가 호출당한 선수를 추월한 경우
        if calling_rank < called_rank:
            # 호출당한 선수의 순위를 하나씩 뒤로 이동
            for i in range(ranks[calling], ranks[players[-1]]):
                current_ranks[i] -= 1
            # 호출당한 선수를 호출한 선수 바로 뒤에 위치시킴
            current_ranks[ranks[calling]-1], current_ranks[ranks[calling]] = current_ranks[ranks[calling]], current_ranks[ranks[calling]-1]
    
    # 현재 순위를 바탕으로 선수 이름을 정렬한 결과 반환
    return [player for _, player in sorted(zip(current_ranks, players))]

👨🏻‍💻 SOMJANG

list index out of range 에러가 발생합니다.

💻 ChatGPT - 실패! ( 성능도 정답도 모두 실패! )

ranking 리스트에서 target이 없는 경우 index 함수는 ValueError를 발생시키기 때문에, try-except 구문으로 예외처리를 해주어야 합니다. 아래는 수정된 코드입니다.

def solution(players, callings):
    ranking = players[:]
    for target in callings:
        try:
            idx = ranking.index(target)
            if idx != 0:
                ranking.pop(idx)
                ranking.insert(0, target)
        except ValueError:
            pass
    return ranking

이 코드에서는 try 구문에서 index 함수를 호출하고, 만약 예외가 발생하면 except 구문에서 아무 작업도 하지 않고 넘어가게 됩니다. 이렇게 함으로써 ranking 리스트에 없는 선수를 찾을 때마다 발생하는 ValueError를 예외처리하여 list index out of range 에러를 방지할 수 있습니다.

👨🏻‍💻 SOMJANG

정답도 성능도 모두 틀렸습니다. 다시 풀어주세요.

💻 ChatGPT - 실패! ( 성능도 정답도 모두 실패! )

죄송합니다. 이전에 작성한 코드가 정확하지 않았습니다. 다음은 정확한 풀이입니다.

def solution(players, callings):
    rankings = [players[0]]
    for calling in callings:
        current_ranking = rankings.index(calling) if calling in rankings else len(rankings)
        if current_ranking == 0:
            continue
        if current_ranking == len(rankings):
            rankings.append(calling)
        else:
            rankings.insert(current_ranking, rankings.pop(current_ranking-1))
    return rankings

이 코드는 다음과 같이 동작합니다.

  1. rankings 리스트에 맨 처음 선수(players[0])를 넣습니다.
  2. callings 리스트를 순회하며, 각각의 호출에 대해 다음을 수행합니다.
    1. calling 선수가 rankings 리스트에 존재하는지 확인합니다. 만약 있다면 그 선수의 등수를 current_ranking 변수에 저장합니다.
    2. calling 선수가 rankings 리스트에 없다면, 그 선수는 가장 마지막 등수가 됩니다. 따라서 current_ranking 변수에는 len(rankings) 값을 저장합니다.
    3. current_ranking 변수가 0인 경우, 즉 calling 선수가 이미 1등인 경우에는 아무 작업도 하지 않습니다.
    4. current_ranking 변수가 len(rankings)와 같은 경우, 즉 calling 선수가 가장 마지막 등수인 경우에는 rankings 리스트의 끝에 그 선수를 추가합니다.
    5. 그 외의 경우에는, calling 선수를 rankings 리스트에서 제거한 후, current_ranking-1 위치에 삽입합니다.
  3. 모든 호출이 끝나면, rankings 리스트를 반환합니다.

이 코드의 시간 복잡도는 O(nm)입니다. 여기서 n은 players 리스트의 길이이고, m은 callings 리스트의 길이입니다. index() 메소드는 리스트에서 값을 찾는 데 O(n) 시간이 소요되므로, 최악의 경우에는 모든 호출마다 O(n) 시간이 소요됩니다. 이는 매우 비효율적인 알고리즘이므로, 더 좋은 방법을 찾아보아야 합니다.

👨🏻‍💻 SOMJANG

다시 처음처럼 문제를 풀어달라고 요청해보았습니다.

💻 ChatGPT - 실패! ( 성능도 정답도 모두 실패! - 런타임 에러까지 발생 )

이 문제는 특정 문자열을 순서대로 처리하면서 해당 문자열을 가진 선수가 추월되면 그 선수의 위치를 바꿔가며 경주를 처리하면 됩니다. 선수들의 초기 위치는 1등부터 순서대로 들어있기 때문에 위치를 바꿔가면서 처리하면 됩니다.

우선 초기 선수들의 순서를 딕셔너리로 만들어 놓습니다. 이 딕셔너리의 키는 선수 이름이고, 값은 그 선수의 현재 위치입니다.

그리고 해설진이 말한 선수가 추월될 때마다 딕셔너리에서 추월당한 선수와 추월한 선수의 위치를 바꿔주면 됩니다. 이때 추월된 선수의 위치를 딕셔너리에서 찾을 수 없으면 추월이 안 된 것이므로 건너뛰면 됩니다.

최종적으로 딕셔너리를 선수 위치 순서대로 정렬하여 선수 이름을 배열에 담아 반환합니다.

아래는 제가 작성한 코드입니다.

다음은 주어진 문제를 해결하기 위한 Python3 코드입니다. 이 코드는 players 배열과 calling 배열의 인덱싱을 최소화하기 위해 딕셔너리를 사용합니다.

def solution(players, callings):
    # 초기 등수는 players의 인덱스와 같음
    ranks = {name: i for i, name in enumerate(players)}
    # 각 선수의 초기 순서도 players의 인덱스와 같음
    orders = [i for i in range(len(players))]
    
    for calling in callings:
        # calling의 순서를 구하기 위해 이전 순서(order)를 기준으로 찾음
        idx = orders[ranks[calling]] - 1
        # calling을 추월한 선수는 idx번째 선수
        # idx번째 선수는 calling 자리로 이동
        ranks[players[idx]], ranks[calling] = ranks[calling], ranks[players[idx]]
        # calling의 순서를 갱신
        orders[ranks[calling]] = orders[ranks[players[idx]]] + 1
        
    # 등수순으로 정렬된 선수 이름 배열 생성
    return [name for name, rank in sorted(ranks.items(), key=lambda x: x[1])]

이 코드의 아이디어는 다음과 같습니다.

  1. 초기 등수는 players 배열의 인덱스와 같습니다. ranks 딕셔너리를 이용하여 각 선수의 등수를 기록합니다.
  2. 초기 순서는 players 배열의 인덱스와 같습니다. orders 리스트를 이용하여 각 선수의 현재 순서를 기록합니다.
  3. 각 calling에 대하여 다음을 반복합니다.
    1. 현재 calling의 순서를 구하기 위해, 이전 순서(order)에서 1을 빼줍니다.
    2. 이전 순서(order)에서 1을 뺀 값(idx)에 해당하는 선수가 calling을 추월한 선수입니다.
    3. 추월한 선수를 calling의 자리로 이동합니다.
    4. 추월한 선수와 calling의 순서를 갱신합니다.
  4. 모든 calling에 대한 처리가 끝나면, ranks 딕셔너리를 등수순으로 정렬하여 선수 이름 배열을 생성합니다.

오늘은 더이상 요청하지 않았습니다 ....IPv6 에 이어 ChatGPT 의 두번째 실패...!

 

GitHub - SOMJANG/CODINGTEST_PRACTICE: 1일 1문제 since 2020.02.07

1일 1문제 since 2020.02.07. Contribute to SOMJANG/CODINGTEST_PRACTICE development by creating an account on GitHub.

github.com

Comments