[프로그래머스] 1. 완주하지 못한 선수 (Lv1), 2. 예상 대진표 (Lv2), 3. 오픈채팅방 (Lv2)

손규성·2022년 10월 28일
0

alogrithm

목록 보기
14/22
post-thumbnail

Lv. 1: 완주하지 못한 선수✍️


문제 설명

수많은 마라톤 선수들이 마라톤에 참여하였습니다. 단 한 명의 선수를 제외하고는 모든 선수가 마라톤을 완주하였습니다. 마라톤에 참여한 선수들의 이름이 담긴 배열 participant와 완주한 선수들의 이름이 담긴 배열 completion이 주어질 때, 완주하지 못한 선수의 이름을 return 하도록 solution 함수를 작성해주세요.

제한사항

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

나의 답안

function solution(participant, completion) {
    participant.sort();
    completion.sort();

    for(let i = 0; i < participant.length; i++) {
      if(participant[i] !== completion[i]) return participant[i];
    }
}

접근 방식:

  1. 완주하지 못한 선수는 단 한 명이다. 즉, completion과 participant 배열은 요소 하나를 제외하면 모두 같은 요소만을 가지고 있다. 또한 각 배열은 선수들의 이름str만 포함하고 있기 때문에 두 배열 모두 오름차순으로 정렬해주었다.

  2. 두 배열 중 길이가 더 긴 partcipant 배열의 length를 기준으로 for문을 돌면서, 동일 인덱스에 위치한 값이 서로 다를 때의 값을 반환해주었다.

회고:

  • 문제를 읽은 후 죽이 되더라도 생각나는 대로 코드를 한번 쭉 작성해보는 습관이 있다. 해당 문제도 똑같이 접근했는데, 바로 답변 처리 되어버렸다. 덕분에 문제의 의도와는 조금 다른 방법으로 답변을 찾는 느낌이 든다. 간결한 코드로 문제를 처리해서 기분은 좋지만, 과연 이게 뿌듯해도 되는 답변인지 잘 모르겠다.

Lv. 2: 예상 대진표✍️


문제 설명

△△ 게임대회가 개최되었습니다. 이 대회는 N명이 참가하고, 토너먼트 형식으로 진행됩니다. N명의 참가자는 각각 1부터 N번을 차례대로 배정받습니다. 그리고, 1번↔2번, 3번↔4번, ... , N-1번↔N번의 참가자끼리 게임을 진행합니다. 각 게임에서 이긴 사람은 다음 라운드에 진출할 수 있습니다. 이때, 다음 라운드에 진출할 참가자의 번호는 다시 1번부터 N/2번을 차례대로 배정받습니다. 만약 1번↔2번 끼리 겨루는 게임에서 2번이 승리했다면 다음 라운드에서 1번을 부여받고, 3번↔4번에서 겨루는 게임에서 3번이 승리했다면 다음 라운드에서 2번을 부여받게 됩니다. 게임은 최종 한 명이 남을 때까지 진행됩니다.

이때, 처음 라운드에서 A번을 가진 참가자는 경쟁자로 생각하는 B번 참가자와 몇 번째 라운드에서 만나는지 궁금해졌습니다. 게임 참가자 수 N, 참가자 번호 A, 경쟁자 번호 B가 함수 solution의 매개변수로 주어질 때, 처음 라운드에서 A번을 가진 참가자는 경쟁자로 생각하는 B번 참가자와 몇 번째 라운드에서 만나는지 return 하는 solution 함수를 완성해 주세요. 단, A번 참가자와 B번 참가자는 서로 붙게 되기 전까지 항상 이긴다고 가정합니다.

제한사항

  • N : 21 이상 220 이하인 자연수 (2의 지수 승으로 주어지므로 부전승은 발생하지 않습니다.)
  • A, B : N 이하인 자연수 (단, A ≠ B 입니다.)

나의 답안

function solution(n, a, b) {
    let count = 0;

    while(a !== b) {
        a = Math.ceil(a / 2);
        b = Math.ceil(b / 2);
        count++;
    }
    return count;
}

접근 방식:

  1. a 선수와 b 선수는 무조건 이겨서 다음 라운드로 진출한다는 점을 생각하면서 풀이했다.

  2. 한 라운드에서 두 명의 선수가 경쟁하기 때문에 a 선수와 b 선수가 만날 때까지 while문을 돌며 각 선수 번호에 /2를 해준다. 이때 선수 번호가 홀수인 경우를 대비해 Math.ceil을 사용해줘 다음 짝수로 변환해준다.

회고:

  • 비슷한 문제를 다른 곳에서 접해본 적이 있어 금방 어떻게 접근해야할지 생각이 났던 문제였다.

Lv. 2: 오픈채팅방(카카오)✍️


문제 설명

카오톡 오픈채팅방에서는 친구가 아닌 사람들과 대화를 할 수 있는데, 본래 닉네임이 아닌 가상의 닉네임을 사용하여 채팅방에 들어갈 수 있다.

신입사원인 김크루는 카카오톡 오픈 채팅방을 개설한 사람을 위해, 다양한 사람들이 들어오고, 나가는 것을 지켜볼 수 있는 관리자창을 만들기로 했다. 채팅방에 누군가 들어오면 다음 메시지가 출력된다.

"[닉네임]님이 들어왔습니다."

채팅방에서 누군가 나가면 다음 메시지가 출력된다.

"[닉네임]님이 나갔습니다."

채팅방에서 닉네임을 변경하는 방법은 다음과 같이 두 가지이다.

채팅방을 나간 후, 새로운 닉네임으로 다시 들어간다.
채팅방에서 닉네임을 변경한다.
닉네임을 변경할 때는 기존에 채팅방에 출력되어 있던 메시지의 닉네임도 전부 변경된다...(전체읽기)

제한사항

record는 다음과 같은 문자열이 담긴 배열이며, 길이는 1 이상 100,000 이하이다.
다음은 record에 담긴 문자열에 대한 설명이다.

  • 모든 유저는 [유저 아이디]로 구분한다.
  • [유저 아이디] 사용자가 [닉네임]으로 채팅방에 입장 - "Enter [유저 아이디][닉네임]" (ex. "Enter uid1234 Muzi")
  • [유저 아이디] 사용자가 채팅방에서 퇴장 - "Leave [유저 아이디]" (ex. "Leave uid1234")
  • [유저 아이디] 사용자가 닉네임을 [닉네임]으로 변경 - "Change [유저 아이디][닉네임]" (ex. "Change uid1234 Muzi")
  • 첫 단어는 Enter, Leave, Change 중 하나이다.
  • 각 단어는 공백으로 구분되어 있으며, 알파벳 대문자, 소문자, 숫자로만 이루어져있다.
  • 유저 아이디와 닉네임은 알파벳 대문자, 소문자를 구별한다.
  • 유저 아이디와 닉네임의 길이는 1 이상 10 이하이다.
  • 채팅방에서 나간 유저가 닉네임을 변경하는 등 잘못 된 입력은 주어지지 않는다.

나의 답안

function solution(record) {
    let answer = [],
        inOutData = record.map(el => el.split(' '));

    let userInfo = {};
    for (let i = 0; i < inOutData.length; i++) {
        if (inOutData[i].length === 3) userInfo[inOutData[i][1]] = inOutData[i][2];
    }
    
    for(let i = 0; i < inOutData.length; i++) {        
        if(inOutData[i][0] === 'Enter') answer.push(`${userInfo[inOutData[i][1]]}님이 들어왔습니다.`)
        if(inOutData[i][0] === 'Leave') answer.push(`${userInfo[inOutData[i][1]]}님이 나갔습니다.`)
    }
    
    return answer;
}

접근 방식:

  1. 코드가 수행해야하는 기능을 크게 두 개로 분류해서 접근했다.

    • 첫째, 각 유저의 고유 ID 값을 기준으로 최종 닉네임값을 할당해주어야 한다.
    • 둘째, 유저가 나가고 들어오는 것을 인식하고 순서대로 기록한다.
  2. 매개변수 record 배열을 map 메서드를 통해 공백을 기준으로 나눠서 inOutData라는 이차원 배열을 만들었다.

  3. 이후 userInfo 라는 객체를 선언하고, inOutData를 순회하면서 inOutData[i]의 길이가 3일 때, 즉 Enter / Leave 이벤트가 발생했을 때를 기준으로 userInfo 객체 안에 ID, 닉네임 값을 업데이트 해주었다.

    • 결국 유저가 마지막으로 나가거나 들어왔을 때의 닉네임만 유저의 ID와 맞게 기록하면 되기 때문이다. (즉 굳이 Change를 참고할 필요 없다)
    • 여기서 조금 더 효율적으로 ID와 닉네임을 업데이트해주는 방법이 있을 것 같은데, 아무리 머리를 굴려봐도 생각나지 않았다.
  4. 이후 다시 inOutData 배열을 순회하며, 각 요소의 첫 번째 값이 'Enter'인 경우 (즉, 유저가 채팅방에 입장했을 때) 해당 ID와 매칭되어 있는 닉네임 + 님이 들어왔습니다.를 answer 배열에 담아주었고, 'Leave'인 경우 ID와 매칭되어 있는 닉네임 + 님이 나갔습니다.를 answer 배열에 담아주었다.

회고:

  • 문제를 풀다 보니 별거 아니었는데 카카오에서 냈던 문제라는 생각과 엄청난 양의 문제 설명에 쫄았던 것 같다.
  • 두 번째 로직은 구현이 너무 쉬웠는데, 아무래도 시간을 많이 잡아 먹었던 부분은 유저의 ID와 닉네임을 알맞게 매칭해주는 과정이었다.
  • 첫 번째 제출에서는 아래 코드로 유저 닉네임 업데이트 로직을 시도했었다.
// 첫 번째 코드에서 사용한 유저별 닉네임 업데이트 부분 
    for(let i = inOutData.length - 1; i >= 0; i--) {
        for(let j = 0; j < i; j++) {
            if(inOutData[j][1] === inOutData[i][1]) {
                inOutData[j][2] = inOutData[i][2];
            }
        }
    }
  • 이렇게 구현하게 되면 몇몇 TC에서 시간초과 or 실패가 뜬다. 난 시간초과 뜨는 것 보고 애초에 이중반복문으로 구현하면 안되는 거구나 싶어서 딱히 디버깅 과정을 진행하진 않았다.
  • 빠르게 노선을 틀어서 객체를 만들어서 ID와 닉네임 정보를 저장하는 방법을 떠올렸지만, 성공적으로 구현하는 데 시간을 좀 썼다.
profile
블로그 이사 → https://sqsung.tistory.com/

0개의 댓글