Algorithm
[CodeKata] 프로그래머스 : 5.30(일), 순위 & 불량 사용자
ttaeng_99
2021. 6. 1. 01:40
반응형
🥋 Oooth More!! (Level 3) : 순위
* 출처 : http://contest.usaco.org/JAN08.htm
🧮 풀이
그래프와 DFS를 통해 풀어보려 했으나 오답률이 높아, 모범답안을 보고 해석리뷰를 하였다.
🖇 리뷰
function solution(n, results) {
var answer = 0;
const graph = Array.from({ length: n+1 },() => Array(n+1).fill(false));
results.map((item) => {
const [win,lose]=item;
graph[win][lose]=1; //이긴 경우 1
graph[lose][win]=-1; //졌을 경우 -1
graph[win][win]=0;//자기자신
graph[lose][lose]=0;
})
const range=[...Array(n).keys()].map(e=>e+1);
for(const mid of range){
for(const a of range){
for(const b of range){
if(graph[a][mid]==1 && graph[mid][b]==1 ){ //a가 mid이기고 mid가 b이김
graph[a][b]=1;
}else if(graph[a][mid]==-1 && graph[mid][b]==-1){//a가 mid한테 지고 mid가 b한테 지면
graph[a][b]=-1;
}
}
}
}
for(const a of range){
let possible=true;
for(const b of range){
if(graph[a][b]===false){
possible=false;
break;
}
}
if(possible){
answer+=1;
}
}
return answer;
}
* 출처 : https://haerang94.tistory.com/330
- 먼저, graph 2차원 배열을 만든다. results 배열을 순회하며, [win][lose] 는 1, [lose][win] 은 -1, 자기자신은 0을 대입한다.
- range는 각 선수들의 번호가 담겨 있는 배열이다. Array.from() 문법으로도 만들 수 있다.
- 이를 3중 순회하며, mid값이 a와 b 사이에 있는 경우 a와 b 사이의 승패관계가 성립될 수 있다. (플로이드 와샬 알고리즘)
- 다시, range를 순회하며 빈 값(false)이 있는 경우엔 break, 모두 값이 있다면 승패관계가 정리된 선수이므로 answer을 1 더한다.
- 플로이드-와샬(Floyd-Warshall) 알고리즘
그래프에서 모든 정점 사이의 최단 경로의 거리를 구하는 알고리즘이다. 이 때, 거쳐가는 정점을 기준으로 알고리즘을 수행하는 것이다.
const INF = Infinity;
const floydWarshall = (dist) => {
const let = dist.length;
for (let mid = 0 ; mid < len ; mid++) {
for (let a = 0 ; a < len ; a++) {
for (let b = 0 ; b < len ; b++) {
if (dist[a][b] > dist[a][mid] + dist[mid][b]) {
dist[a][b] = dist[a][mid] + dist[mid][b];
}
}
}
}
};
const solution = (function() {
const graph = [
[0, 5, INF, 8],
[7, 0, 9, INF],
[2, INF, 0, 4],
[INF, INF, 3, 0]
];
floydWarshall(graph);
return graph;
}());
위 예시는, 두 정점간의 이동경로 vs 특정 정점을 거치는 이동경로 중, 소요시간이 적은 경로를 저장하는 플로이드 와샬 알고리즘이다.
이를 통해, 더 나은 소요시간(혹은 가중치)로 최신화할 수 있으며, 상위 문제처럼 승패를 가중치로 표현하며 그래프 관계도를 짤 수 있다.
🥋 Oooth More!! (Level 3) : 불량 사용자
🧮 풀이
function solution(user_id, banned_id) {
let banList = [];
banned_id.forEach(id => {
let list = [];
let strs = id.split("").reduce((acc, cur, idx) => {
return cur === "*" ? acc : [...acc, [cur, idx]];
}, [])
user_id.forEach(user => {
if (user.length === id.length && strs.every(str => user[str[1]] === str[0])) {
list.push(user)
}
})
banList.push(list);
})
let cases = [];
function dfs(idx, list) {
if (idx === banList.length) {
list = JSON.stringify(list.sort());
if (!cases.includes(list)) {
cases.push(list)
}
}
else {
for (let ele of banList[idx]) {
if (!list.includes(ele)) {
dfs(idx+1, [...list, ele])
}
}
}
}
dfs(0, [])
return cases.length;
}
1. banList user_id 후보들의 배열 만들기
- banList 배열은 각각의 banned_id 에 해당될 수 있는 user_id 후보들의 집합을 배열로 저장한다. 이중 forEach로 탐색한다.
- 먼저, banned_id의 각각의 id를 strs로 편집한다. 이는, *을 제거하고, 글자만 ['글자', '인덱스'] 튜플 형태로 변형한 것이다.
- 다음으로 user_id를 순회하며, 1) 해당 id와 글자수가 같고, 2) strs의 모든 요소를 만족하는 id값들만을 list에 저장한다.
- 이렇게, list에 현재 banned_id 경우에 해당될 수 있는 id들이 모이면, 이를 최종적으로 banList에 push() 해준다.
2. banList 후보들의 조합을 DFS로 구하기
- dfs() 함수를 통해 banList의 모든 배열을 순회하며 가능한 경우의 수의 조합들을 만들어 cases 배열에 push() 하는 것이다.
- dfs() 함수는 idx(인덱스), list(경우의 수 누적배열) 을 매개변수로 받는다. idx가 banList.length와 같다면 탐색이 완료된다.
- 탐색 완료 시, 배열 비교를 위해 list를 JSON으로 만든다. 이 값이 cases에 없다면 push() 해준다. (개수만 필요하므로 includes() 탐색에 용이한 JSON string으로 담음)
- 탐색 미완료 시, banList[idx] id 후보들의 배열을 순회하며, 해당 값이 누적하고있는 list에 없는 경우에만 dfs() 를 호출한다.
🖇 리뷰
"불량 사용자" 문제의 경우, DFS & BFS 어느 방법을 사용해도 무방하다. (결국은 완전탐색 알고리즘을 구현하는 것이 목적)
다양한 방법들이 있지만, 나와 유사한 방법을 쓴 경우도 있었기에 이 방법을 권장하는 바이다.
반응형