[프로그래머스 lv.1] 달리기 경주 (JS)

프로그래머스 - 달리기 경주

 

프로그래머스

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

programmers.co.kr

 

문제 설명과 입출력이 직관적으로 다가와서 쉬운 편이라고 생각했으나..

객체와 배열의 특징에 대해 다시 한 번 생각을 정리할 수 있는 문제였다!

 

 

문제

 

 

첫 시도 (오답)

callings.forEach(player => {
  let curRank = players.indexOf(player);
  let tmp = players[curRank - 1];
  players[curRank - 1] = player;
  players[curRank] = tmp;
});

return players;

 

indexOf를 활용해 이름이 불린 선수의 현재 등수를 찾고, 앞뒤 요소(선수)를 바꿔줬다.

간단하게 풀릴 줄 알았으나 4가지 케이스에서 시간초과가 떴다.

indexOf로 배열의 앞에서부터 선수를 찾는 과정이 반복되고, 이 동작은 탐색하는 배열이 길어질수록 + 여러번 탐색할수록 시간을 많이 소요하는 것이 문제겠구나 생각했다.

 

 

재도전 (정답)

let rank_of_players = {};
let player_of_rank = {};
    
players.forEach((player, rank) => {
   rank_of_players[player] = rank;
    player_of_rank[rank] = player;
});
    
callings.forEach(player => {
   let cur_rank = rank_of_players[player];
    let ahead_player = player_of_rank[cur_rank - 1];
    
    rank_of_players[player] = cur_rank - 1;
    rank_of_players[ahead_player] = cur_rank;
    
    player_of_rank[cur_rank] = ahead_player;
    player_of_rank[cur_rank - 1] = player;
});
    
let sort_arr = Object.entries(rank_of_players).sort(([,a], [,b]) => a - b);
let answer = sort_arr.map(([player, _]) => {
   return player; 
});
    
return answer;

 

순서대로 선수의 이름이 있는지 확인하는 방법이 아닌, 선수의 이름으로 바로 등수를 확인할 수 있는 방법을 생각했다.

바로 key-value 쌍으로 구성된 객체를 이용하자 생각이 들었다.

그런데 {player: rank} 구조라면 이름이 불린 선수의 앞에 달리고있는 선수를 찾을 때 value를 갖고있는 key를 찾아야할텐데.. 그럼 결국 앞에서부터 차례차례 value를 확인하는 수밖에 없지않나..? 그럼 indexOf를 사용하는 거랑 같은 결과 아닌가? 하는 고민이 들었다.

이 때부터 약간 함정에 빠지긴했지만.. rank로 바로 player를 찾을 수 있도록 {rank: player} 구조의 객체를 하나 더 만들어서 관리하기로 했다.

 

두 객체를 기반으로 이름이 불리는 선수와 앞에 달리던 선수의 등수를 바꿔주고, 각 랭킹에 해당하는 선수들의 이름도 업데이트 해줬다.

객체는 sort를 지원하지 않기에, [key, value] 형태의 배열이 담긴 배열로 만들어주고 두번째 원소인 value를 기준으로 정렬해주었다.

그 후 첫번째 원소인 선수의 이름만 따로 배열로 만들어 반환했다.

 

그런데 비교적 간단한 문제에 너무 많은 과정과 여러번의 배열 생성이 있다는 것이 효율적이진 않은 것 같다는 생각이 들었다.

제출 후 객체를 사용해 풀이한 사람들의 풀이를 더 살펴보았다.

 

 

재도전2 (정답)

let rank_of_players = {};
players.forEach((player, rank) => {
   rank_of_players[player] = rank;
});

callings.forEach(player => {
   // 객체 업데이트
    let prev_rank = rank_of_players[player];
    rank_of_players[player] = prev_rank - 1;
    let ahead_player = players[prev_rank -1];
    rank_of_players[ahead_player] = prev_rank;
    
    // 배열 업데이트
    players[prev_rank] = ahead_player;
    players[prev_rank - 1] = player;
});

return players;

ㅎㅎㅎ 다른 사람들의 코드를 보다가 깨달았다.

내가 따로 만들어주지 않아도 이미 숫자로 빠르게 이름을 알아낼 수 있는 자료구조가 있다는 것을..

심지어 그건 정렬해주지 않아도 이미 빠른 순서대로 정렬이 되어있다는 것을..

 

위의 코드와 유사하지만 정리해보자면

1) 이름이 불린 선수의 등수를 빠르게 찾을 수 있도록 rank_of_players 객체 생성 ({선수 이름: 등수} 구조)

→ 각 등수에 해당하는 선수를 알기위한 객체는 만들 필요가 없다. 이미 players 배열에 순서대로 저장돼있기 때문에

 

2) 이름이 불린 선수의 등수를 저장해두고, 한단계 앞 등수로 바꿔주기

 

3) 이름이 불린 선수의 앞에서 달리고있던 선수를 players 배열에서 인덱스를 활용해 가져오기

 

4) 불러온 선수의 등수를 바꿔주기 (rank_of_players 객체)

 

5) 배열에서도 이름불린 선수 ↔ 앞에 달리던 선수 순서 바꿔주기

 

 

기억해둘 것

나는 어떤 값을 찾기위해서 이런 값을 사용할 수 있는데 그럼 어떤 형태로 데이터를 관리하는게 효율적일까? 생각해보는 것이 필요한 것 같다.

객체 (key-value): 문자열로 인덱스를 찾기위해 반복문을 사용하는 대신 바로 접근할 수 있는 객체를 사용함

배열: 인덱스에 해당하는 값을 찾기위해 사용함 순서대로 정렬되어있고, n번째에 해당하는 값이 무엇인지 찾을 때는 배열을 활용하자

'알고리즘' 카테고리의 다른 글

탐욕 알고리즘 (Greedy Algorithm)  (0) 2024.06.29
투포인터 알고리즘 (Two Pointers Algorithm)  (0) 2024.06.26