Skip to content

Commit

Permalink
feat: solve pg200-82
Browse files Browse the repository at this point in the history
  • Loading branch information
jjanmo committed Oct 3, 2023
1 parent 1ad8df0 commit 7f45163
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/programmers200.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,4 @@
| 79 | 23.09.24 | | [그림 확대](https://school.programmers.co.kr/learn/courses/30/lessons/181836) | Done | [✔️](/pg200/79.md) | |
| 80 | 23.09.27 | | [다항식 더하기](https://school.programmers.co.kr/learn/courses/30/lessons/120863) | Done | | |
| 81 | 23.09.28 | | [오픈채팅방](https://school.programmers.co.kr/learn/courses/30/lessons/42888) | Done | [✔️](/pg200/81.md) | |
| 82 | 23.10.03 | | [순위 검색](https://school.programmers.co.kr/learn/courses/30/lessons/72412) | Done | [✔️](/pg200/82.md) | |
105 changes: 105 additions & 0 deletions pg200/82.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# 순위검색

> 언제나 시간초과
이 문제는 노골적으로 `정확성과 효율성 테스트`를 모두 실행한다고 적혀있었다.

정확성만을 위한 코드에 대한 로직은 그다지 어렵지 않았다. 각각의 정보를 파싱(?)하여 유저 정보로 담은 뒤에 쿼리에 따라서 해당 정보를 필터하면 되는 로직이다.
설명에서처럼 쿼리들을 순회하면서 각 쿼리 조건에 해당하는 유저 정보를 필터링해야하기때문에 자연스럽게 이중for문으로 인한 시간초과가 발생한다.

그래서 각 조건에 따른 분류된 유저 정보를 미리 만들어서 해당 조건에 담긴 유저 정보들로만 필터링 하면 되겠다라고 생각했다. 하지만 여기서 문제는 조건 중에 모두를 포함하는 '-' 표시가 있었다.

```
lang : cpp | java | python → 언어
pos : backend | frontend → 포지션
career : junior | senior → 경력
food : chicken | pizza → 소울푸드
```

> `3 * 2 * 2 * 2` = 24가지
이런식으로만 키값을 나누면 24가지의 키값을 가진 유저 정보로 분류하면 되겠다고 생각했지만 실제로는 `-`, 모두 포함이라는 조건도 추가해줘야했다.

```
lang : cpp | java | python | -
pos : backend | frontend | -
career : junior | senior | -
food : chicken | pizza | -
```

> `4 * 3 * 3 * 3` = 108가지
> `-` 를 추가하는 부분에서 사실 조금 망설임이 있었다. 과연 이렇게 하는 것이 맞을까에 대해서 말이다. 뭔가 `노가다` 같은 느낌이랄까. 다른 힌트들을 보다가 나의 생각에 확신이 들어서 이런 접근법으로 풀이를 재구현하였다.
결과적으로 108가지의 키값을 가진 유저 정보로 분류를 해야했다. 사실 108가지는 그렇게 많은 분류가 아니기에 이정도면 구현할법하기도 했다. 어떻게 이것을 구현할지에 대한 로직이 중요했다. 108가지이기 때문에 for문의 연속으로 해도 전혀 문제가 되지않는다고 생각했다.

이런식으로 변경 후에 filter메소드를 이용하여 마지막 개수를 구했다. 그런데, 이렇게 해도 시간초과는 여전하였다. 문제는 바로 마지막의 filter였다. 힌트를 찾아보니 이 부분에 대한 문제는 `이분탐색법(이진탐색)`을 통해서 해결해야한다는 것을 알게되었다. 수가 커진다면 filter보다는 이진탐색을 통해서 거르는 방법이 훨씬 효율적이다라는 것은 이번에 경험적으로 알게되었다.

처음엔 어떻게든 해쉬 자료구조(O(1))로 변형시키기 위해서 노력해봤지만 더이상의 효율을 높이지 못했다. 위에서 언급한대로 힌트를 통해서 이진탐색이라는 힌트를 얻었고 결국 해결하였다. 시간초과가 해결되지않는다면, 이제 이진탐색도 효율을 높이기 위한 한가지 방법이라는 것을 기억하자.

제출 후에 다른 사람들 풀이를 보았다. 이 문제 자체가 그렇게 고운(?) 풀이로 풀리는 문제가 아닌가보다. 다들 코드가 길고 깔끔하다는 느낌은 못받았다. 하지만 전체적인 로직자체는 유사하였다. 어떻게든 O(1)로 유저 정보에 접근하여 필터링하려는 로직말이다.

오늘도 하나를 배운다. 🙏🏻

> 최종 문제 풀이
```js
function solution(info, query) {
const langType = ['cpp', 'java', 'python', '-'];
const posType = ['backend', 'frontend', '-'];
const careerType = ['junior', 'senior', '-'];
const foodType = ['chicken', 'pizza', '-'];
const users = {};

// users 초기화
for (let lang of langType) {
for (let pos of posType) {
for (let career of careerType) {
for (let food of foodType) {
users[`${lang}-${pos}-${career}-${food}`] = [];
}
}
}
}

// users 정보 채우기
info.forEach((str) => {
const [lang, pos, career, food, score] = str.split(' ');
for (let l of [lang, '-']) {
for (let p of [pos, '-']) {
for (let c of [career, '-']) {
for (let f of [food, '-']) {
users[`${l}-${p}-${c}-${f}`].push(Number(score));
}
}
}
}
});

// 이진탐색을 위한 정렬
for (let key in users) {
users[key].sort((a, b) => a - b);
}

return query.map((q) => {
const [lang, pos, career, food, score] = q
.split(' ')
.filter((w) => w !== 'and');

const scores = users[`${lang}-${pos}-${career}-${food}`];

// 이진탐색, if scores.filter(....) 이런식으로 하면 시간초과를 해결하지 못함!
let left = 0,
right = scores.length - 1;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
if (score > scores[mid]) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return scores.length - left;
});
}
```

0 comments on commit 7f45163

Please sign in to comment.