본문 바로가기

Coding Study

[## 프로그래머스 coding study ##: 2024.02]

2024년 2월에 푼 프로그래머스 코딩문제("코딩테스트 공부 목적")
본 글은 프로그래머스 코딩테스트 문제에서 푼 문제를 정리한 글입니다.

 

1. [LV0] 등수 매기기(정답률 73%)

[문제]

영어 점수와 수학 점수의 평균 점수를 기준으로 학생들의 등수를 매기려고 합니다. 영어 점수와 수학 점수를 담은 2차원 정수 배열 score가 주어질 때, 영어 점수와 수학 점수의 평균을 기준으로 매긴 등수를 담은 배열을 return하도록 solution 함수를 완성해주세요.

score result
[[80, 70], [90, 50], [40, 70], [50, 80]] [1, 2, 4, 3]
[[80, 70], [70, 80], [30, 50], [90, 100],
[100, 90], [100, 100], [10, 30]]
[4, 4, 6, 2]

 

[코드]

def solution(score):
   
    ## 2차원 배열 만들기: 2 x len(score)
    ## answer[0]에는 수학 점수와 영어 점수 저장
    ## answer[1]에는 rank 저장
    answer = [[0 for i in range(len(score))] for j in range(2)]
    answer[0] = [(i+j)/2 for i,j in score]
    
    ## 점수 내림차순 정렬
    ## 중복을 허용하지 않기 위해 집합 set 사용
    rank = list(set(answer[0]))
    rank.sort(reverse=True)
    
    ### score 값은 1로 초기화
    score = 1
    for r in rank:
        num = 0
        for index, key in enumerate(answer[0]):
            if r == key:
                answer[1][index] = score
                num += 1
        ## 같은 점수가 여러 개인 경우 등수가 밀리기 때문에 그에 대해서 반영        
        score += num
            
    return answer[1]

2. [Lv1] 가장 많이 받은 선물(정답률 22%, 2024 KAKAO WINTER INTERSHIP)

[문제]

선물을 직접 전하기 힘들 때 카카오톡 선물하기 기능을 이용해 축하 선물을 보낼 수 있습니다. 당신의 친구들이 이번 달까지 선물을 주고받은 기록을 바탕으로 다음 달에 누가 선물을 많이 받을지 예측하려고 합니다.

  • 두 사람이 선물을 주고받은 기록이 있다면, 이번 달까지 두 사람 사이에 더 많은 선물을 준 사람이 다음 달에 선물을 하나 받습니다.
    • 예를 들어 A가 B에게 선물을 5번 줬고, B가 A에게 선물을 3번 줬다면 다음 달엔 A가 B에게 선물을 하나 받습니다.
  • 두 사람이 선물을 주고받은 기록이 하나도 없거나 주고받은 수가 같다면, 선물 지수가 더 큰 사람이 선물 지수가 더 작은 사람에게 선물을 하나 받습니다.
    • 선물 지수는 이번 달까지 자신이 친구들에게 준 선물의 수에서 받은 선물의 수를 뺀 값입니다.
    • 예를 들어 A가 친구들에게 준 선물이 3개고 받은 선물이 10개라면 A의 선물 지수는 -7입니다. B가 친구들에게 준 선물이 3개고 받은 선물이 2개라면 B의 선물 지수는 1입니다. 만약 A와 B가 선물을 주고받은 적이 없거나 정확히 같은 수로 선물을 주고받았다면, 다음 달엔 B가 A에게 선물을 하나 받습니다.
    • 만약 두 사람의 선물 지수도 같다면 다음 달에 선물을 주고받지 않습니다.

위에서 설명한 규칙대로 다음 달에 선물을 주고받을 때, 당신은 선물을 가장 많이 받을 친구가 받을 선물의 수를 알고 싶습니다.

친구들의 이름을 담은 1차원 문자열 배열 friends 이번 달까지 친구들이 주고받은 선물 기록을 담은 1차원 문자열 배열 gifts가 매개변수로 주어집니다. 이때, 다음달에 가장 많은 선물을 받는 친구가 받을 선물의 수를 return 하도록 solution 함수를 완성해 주세요.

 

[입출력 예]

friends gifts result
[ "muzi", "ryan", "frodo", "neo" ] ["muzi frodo", "muzi frodo", "ryan muzi", "ryan muzi", "ryan muzi", "frodo muzi", "frodo ryan", "neo muzi"] 2
["joy", "brad", "alessandro", "conan", "david"] ["alessandro brad", "alessandro joy", "alessandro conan", "david alessandro", "alessandro david"] 4
["a", "b", "c"] ["a b", "b a", "c a", "a c", "a c", "c a"] 0

[코드]

def solution(friends, gifts):
    #############################################################
    ## 2차원 배열 생성 ## 두 사람이 선물을 주고 받은 기록 data에 명시하기
    ## 모두 "NA"로 초기화
    data = [["NA" for i in range(len(friends))] for j in range(len(friends))]
    name_index = {name: index for index,name in enumerate(friends)}
    
    for k in gifts:
        give, receive = k.split(" ")
        row=name_index[give]; col=name_index[receive]
        ## 두 사람이 선물을 주고 받은 기록이 존재하는 경우 0으로 초기화 하고 주고 받은 횟수만큼 더하기
        ## 두 사람이 선물을 주고 받은 기록이 없는 경우는 "NA" 유지
        if data[row][col] == "NA":
            data[row][col] = 0; data[col][row] = 0
        data[row][col] += 1
    #############################################################
    
    #############################################################
    ## 선물지수 구하기
    present = [0 for i in range(len(friends))]
    
    for name in friends:
        index = name_index[name]
        present[index] = sum(data[index][i] for i in range(len(present)) if data[index][i] != "NA") - sum(data[j][index] for j in range(len(present)) if data[j][index] != "NA")
    #############################################################
    
    #############################################################
    ## 다음달에 받을 선물의 수 계산하기
    answer = [0 for i in range(len(friends))]
    
    for row in range(len(friends)): 
        for col in range(row,len(friends)):
        ## (조건 1) 선물을 주고 받은 기록이 있으면서 주고 받은 횟수가 다른 경우
            if data[row][col] != "NA" and data[row][col] != data[col][row]:
                if data[row][col] > data[col][row]: ## A가 B에게 선물을 준 경우가 받은 경우보다 많은 경우
                    answer[row] += 1
                elif data[row][col] < data[col][row]:
                    answer[col] += 1 ## B가 A에게 선물을 준 경우가 받은 경우보다 많은 경우
        ## (조건 2) 선물을 주고 받은 기록이 없거나 주고 받은 횟수가 같은 경우, 선물지수로 비교
            else:
                if present[row] > present[col]:
                    answer[row] += 1
                elif present[row] < present[col]:
                    answer[col] += 1
    #############################################################                
    return max(answer)

3. [LV1] [PCCP 기출문제] 1번/붕대 감기(정답률 31%)

[문제]

어떤 게임에는 붕대 감기라는 기술이 있습니다.

붕대 감기 t초 동안 붕대를 감으면서 1초마다 x만큼의 체력을 회복합니다. t초 연속으로 붕대를 감는 데 성공한다면 y만큼의 체력을 추가로 회복합니다. 게임 캐릭터에는 최대 체력이 존재해 현재 체력이 최대 체력보다 커지는 것은 불가능합니다.

기술을 쓰는 도중 몬스터에게 공격을 당하면 기술이 취소되고, 공격을 당하는 순간에는 체력을 회복할 수 없습니다. 몬스터에게 공격당해 기술이 취소당하거나 기술이 끝나면 그 즉시 붕대 감기를 다시 사용하며, 연속 성공 시간이 0으로 초기화됩니다.

몬스터의 공격을 받으면 정해진 피해량만큼 현재 체력이 줄어듭니다. 이때, 현재 체력이 0 이하가 되면 캐릭터가 죽으며 더 이상 체력을 회복할 수 없습니다.

당신은 붕대감기 기술의 정보, 캐릭터가 가진 최대 체력과 몬스터의 공격 패턴이 주어질 때 캐릭터가 끝까지 생존할 수 있는지 궁금합니다.

붕대 감기 기술의 시전 시간, 1초당 회복량, 추가 회복량을 담은 1차원 정수 배열 bandage와 최대 체력을 의미하는 정수 health, 몬스터의 공격 시간과 피해량을 담은 2차원 정수 배열 attacks가 매개변수로 주어집니다. 모든 공격이 끝난 직후 남은 체력을 return 하도록 solution 함수를 완성해 주세요. 만약 몬스터의 공격을 받고 캐릭터의 체력이 0 이하가 되어 죽는다면 -1을 return 해주세요.

 

제한사항

  • bandage는 [시전 시간, 초당 회복량, 추가 회복량] 형태의 길이가 3인 정수 배열입니다.
    • 1 ≤ 시전 시간 = t ≤ 50
    • 1 ≤ 초당 회복량 = x ≤ 100
    • 1 ≤ 추가 회복량 = y ≤ 100
  • 1 ≤ health ≤ 1,000
  • 1 ≤ attacks의 길이 ≤ 100
    • attacks[i]는 [공격 시간, 피해량] 형태의 길이가 2인 정수 배열입니다.
    • attacks는 공격 시간을 기준으로 오름차순 정렬된 상태입니다.
    • attacks의 공격 시간은 모두 다릅니다.
    • 1 ≤ 공격 시간 ≤ 1,000
    • 1 ≤ 피해량 ≤ 100

[입출력 예]

bandage health attacks result
[5, 1, 5] 30 [[2, 10], [9, 15], [10, 5], [11, 5]] 5
[3, 2, 7] 20 [[1, 15], [5, 16], [8, 6]] -1
[4, 2, 7] 20 [[1, 15], [5, 16], [8, 6]] -1
[1, 1, 1] 5 [[1, 2], [3, 2]] 3

[코드]

def solution(bandage, health, attacks):
    ####### answer: 체력, success: 연속 성공 횟수#######
    ####### t: 사전 시간, x: 초당 회복량, y: 추가 회복량 #######
    answer=health; t,x,y = bandage; success = 0
    dict_attack = dict(attacks)
    
    for time in range(1, attacks[-1][0] + 1):
        ####### 현재 체력이 최대 체력보다 작은 경우, 1초당 health만큼 체력이 회복 & 몬스터에게 공격 X #######
        if (answer < health) and (time not in list(dict_attack.keys())) and (answer > 0):
            answer += x
            success += 1
        ####### 몬스터에게 공격 O #######
        elif time in list(dict_attack.keys()):
            answer -= dict_attack[time] ## 공격 0
            success = 0 ## 연속 성공횟수 0으로 초기화
        
        ####### t초 연속으로 붕대 감는 데를 성공한 경우, y만큼의 체력 회복 #######    
        if success == t:
            answer += y
            success = 0
        
        ####### 만약, 체력이 최대 체력보다 큰 경우, 최대 체력으로 보정 #######
        if answer>health:
            answer = health
            
    if answer <= 0:
        return -1
    else:
        return answer

4. [LV1] 신고 결과 받기(정답률 37%, 2024 KAKAO WINTER INTERSHIP)

[문제]

신입사원 무지는 게시판 불량 이용자를 신고하고 처리 결과를 메일로 발송하는 시스템을 개발하려 합니다. 무지가 개발하려는 시스템은 다음과 같습니다.

  • 각 유저는 한 번에 한 명의 유저를 신고할 수 있습니다.
    • 신고 횟수에 제한은 없습니다. 서로 다른 유저를 계속해서 신고할 수 있습니다.
    • 한 유저를 여러 번 신고할 수도 있지만, 동일한 유저에 대한 신고 횟수는 1회로 처리됩니다.
  • k번 이상 신고된 유저는 게시판 이용이 정지되며, 해당 유저를 신고한 모든 유저에게 정지 사실을 메일로 발송합니다.
    • 유저가 신고한 모든 내용을 취합하여 마지막에 한꺼번에 게시판 이용 정지를 시키면서 정지 메일을 발송합니다.

다음은 전체 유저 목록이 ["muzi", "frodo", "apeach", "neo"]이고, k = 2(즉, 2번 이상 신고당하면 이용 정지)인 경우의 예시입니다.

유저 ID 유저가 신고한 ID 설명
"muzi" "frodo" "muzi"가 "frodo"를 신고했습니다.
"apeach" "frodo" "apeach"가 "frodo"를 신고했습니다.
"frodo" "neo" "frodo"가 "neo"를 신고했습니다.
"muzi" "neo" "muzi"가 "neo"를 신고했습니다.
"apeach" "muzi" "apeach"가 "muzi"를 신고했습니다.

각 유저별로 신고당한 횟수는 다음과 같습니다.

유저 ID 신고당한 횟수
"muzi" 1
"frodo" 2
"appeach" 0
"neo" 2

위 예시에서는 2번 이상 신고당한 "frodo"와 "neo"의 게시판 이용이 정지됩니다. 이때, 각 유저별로 신고한 아이디와 정지된 아이디를 정리하면 다음과 같습니다.

유저 ID 유저가 신고한 ID 정지된 ID
"muzi" ["frodo", "neo"] ["frodo", "neo"]
"frodo" ["neo"] ["neo"]
"apeach" ["muzi", "frodo"] ["frodo"]
"neo" 없음 없음

따라서 "muzi"는 처리 결과 메일을 2회, "frodo"와 "apeach"는 각각 처리 결과 메일을 1회 받게 됩니다.

 

이용자의 ID가 담긴 문자열 배열 id_list, 각 이용자가 신고한 이용자의 ID 정보가 담긴 문자열 배열 report, 정지 기준이 되는 신고 횟수 k가 매개변수로 주어질 때, 각 유저별로 처리 결과 메일을 받은 횟수를 배열에 담아 return 하도록 solution 함수를 완성해주세요.

 

[제한시간 안내]

  • 정확성 테스트: 10초 

 

[코드]

def solution(id_list, report, k):
    ###### 각 유저가 신고당한 횟수 계산하기 ######
    ###### 주의: 한 유저가 여러번 신고한 경우, 1회로 처리 ######
    count = {i:0 for i in id_list}
    for j in list(set(report)):
        user1, user2 = j.split() ## user1: 신고한 ID, user2: 신고당한 ID
        count[user2] += 1
    
    ###### 신고당한 횟수가 k번 이상인 유저 확인 ######
    stop_user = []
    for keys, values in count.items():
        if values >= k:
            stop_user.append(keys)
    
    ###### 이메일이 정지되는 유저를 신고한 유저에게 메일 전송하는 횟수 계산 ######
    answer = {i:0 for i in id_list}
    for j in list(set(report)):
        user1, user2 = j.split()
        if user2 in stop_user:
            answer[user1] += 1
            
    answer = list(answer.values())   
    return answer

4. [LV1] 공원 산책(정답률 42%)

[문제]

지나다니는 길을 'O', 장애물을 'X'로 나타낸 직사각형 격자 모양의 공원에서 로봇 강아지가 산책을 하려합니다. 산책은 로봇 강아지에 미리 입력된 명령에 따라 진행하며, 명령은 다음과 같은 형식으로 주어집니다.

  • ["방향 거리", "방향 거리" … ]

예를 들어 "E 5"는 로봇 강아지가 현재 위치에서 동쪽으로 5칸 이동했다는 의미입니다. 로봇 강아지는 명령을 수행하기 전에 다음 두 가지를 먼저 확인합니다.

  • 주어진 방향으로 이동할 때 공원을 벗어나는지 확인합니다.
  • 주어진 방향으로 이동 중 장애물을 만나는지 확인합니다.

위 두 가지중 어느 하나라도 해당된다면, 로봇 강아지는 해당 명령을 무시하고 다음 명령을 수행합니다.
공원의 가로 길이가 W, 세로 길이가 H라고 할 때, 공원의 좌측 상단의 좌표는 (0, 0), 우측 하단의 좌표는 (H - 1, W - 1) 입니다.

 

공원을 나타내는 문자열 배열 park, 로봇 강아지가 수행할 명령이 담긴 문자열 배열 routes가 매개변수로 주어질 때, 로봇 강아지가 모든 명령을 수행 후 놓인 위치를 [세로 방향 좌표, 가로 방향 좌표] 순으로 배열에 담아 return 하도록 solution 함수를 완성해주세요.

 

[제한사항]

  • 3 ≤ park의 길이 ≤ 50
    • 3 ≤ park[i]의 길이 ≤ 50
      • park[i]는 다음 문자들로 이루어져 있으며 시작지점은 하나만 주어집니다.
        • S : 시작 지점
        • O : 이동 가능한 통로
        • X : 장애물
    • park는 직사각형 모양입니다.
  • 1 ≤ routes의 길이 ≤ 50
    • routes의 각 원소는 로봇 강아지가 수행할 명령어를 나타냅니다.
    • 로봇 강아지는 routes의 첫 번째 원소부터 순서대로 명령을 수행합니다.
    • routes의 원소는 "op n"과 같은 구조로 이루어져 있으며, op는 이동할 방향, n은 이동할 칸의 수를 의미합니다.
      • op는 다음 네 가지중 하나로 이루어져 있습니다.
        • N : 북쪽으로 주어진 칸만큼 이동합니다.
        • S : 남쪽으로 주어진 칸만큼 이동합니다.
        • W : 서쪽으로 주어진 칸만큼 이동합니다.
        • E : 동쪽으로 주어진 칸만큼 이동합니다.
      • 1 ≤ n ≤ 9

[입출력 예]

park routes result
["SOO","OOO","OOO"] ["E 2","S 2","W 1"] [2,1]
["SOO","OXX","OOO"] ["E 2","S 2","W 1"] [0,1]
["OSO","OOO","OXO","OOO"] ["E 2","S 3","W 1"] [0,0]

[입출력 예 설명]

<<<< 입출력 예 #1 >>>>

입력된 명령대로 동쪽으로 2칸, 남쪽으로 2칸, 서쪽으로 1칸 이동하면 [0,0] -> [0,2] -> [2,2] -> [2,1]이 됩니다.

 

<<<< 입출력 예 #2 >>>>

입력된 명령대로라면 동쪽으로 2칸, 남쪽으로 2칸, 서쪽으로 1칸 이동해야하지만 남쪽으로 2칸 이동할 때 장애물이 있는 칸을 지나기 때문에 해당 명령을 제외한 명령들만 따릅니다. 결과적으로는 [0,0] -> [0,2] -> [0,1]이 됩니다.

 

<<<< 입출력 예 #3 >>>>

처음 입력된 명령은 공원을 나가게 되고 두 번째로 입력된 명령 또한 장애물을 지나가게 되므로 두 입력은 제외한 세 번째 명령만 따르므로 결과는 다음과 같습니다. [0,1] -> [0,0]

 

[코드]

def move(x, y, direction, step, obstacle, width, height):
    for i in range(1,int(step)+1):
    	################### 동쪽으로 이동하는 경우 ###################
        if direction=="E": 
        	### 장애물이 존재하는 경우 이동 X ###
            if [x,y+i] in obstacle:
                print("do not move")
                break
            ### 공원을 벗어나지 않고 장애물이 존재하지 않은 경우 업데이트 ###
            if (i==int(step)) and (y+int(step) <= width-1):
                y += int(step)
                break
        ################### 서쪽으로 이동하는 경우 ###################
        if direction=="W":
            if [x, y-i] in obstacle:
                print("do not move")
                break
                
            if (i==int(step)) and (y-int(step) >= 0):
                y -= int(step)
                break
        ################### 북쪽으로 이동하는 경우 ###################
        if direction=="N":
            if [x-i, y] in obstacle:
                print("do not move")
                break
                
            if (i==int(step)) and (x-int(step) >= 0):
                print("move")
                x -= int(step)
                break
        ################### 남쪽으로 이동하는 경우 ###################
        if direction=="S":
            if [x+i, y] in obstacle:
                print("do not move")
                break
                
            if (i==int(step)) and (x+int(step) <= height-1):
                x += int(step)
                break
        print(x,y)
    return x,y

def solution(park, routes):
    ###### 공원의 가로(세로) 길이 ######
    height = len(park); width = len(park[0])
    
    ###### 시작 지점 및 장애물 위치 찾기 ######
    obstacle = []
    for i,j in enumerate(park):
        if "S" in j:
            x,y = [i,j.index("S")]
        if "X" in j:
            obstacle += [[i, index] for index in range(j.index('X'), width) if j[index]=="X"]
    print("obstacle", obstacle)
    ###### 해당 명령에 따라 강아지 이동 ###
    for go in routes:
        print(go[0])
        x,y = move(x, y, go[0], go[-1], obstacle, width, height)
        print("finish", x,y)
    return [x,y]

5. [Lv1] 달리기 경주(정답률 43%)

[문제]

얀에서는 매년 달리기 경주가 열립니다. 해설진들은 선수들이 자기 바로 앞의 선수를 추월할 때 추월한 선수의 이름을 부릅니다. 예를 들어 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"]

 

[코드]

def solution(players, callings):
    player_indices = {player: index for index, player in enumerate(players)}
    for player1 in callings:
        index = player_indices[player1]; player2 = players[index-1]
        players[index] = player2; players[index - 1] = player1
        player_indices[player2] = index; player_indices[player1] = index-1
    return players

※ 이 문제는 알고리즘이 어렵기 보다는 시간초과 문제가 존재했습니다.

※ 시간 초과 문제를 해결하기 위해 index 함수대신 dictionary를 이용하였습니다.