ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [CodeKata] 프로그래머스 : 5.30(일), 순위 & 불량 사용자
    Algorithm 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

    1. 먼저, graph 2차원 배열을 만든다. results 배열을 순회하며, [win][lose] 는 1, [lose][win] 은 -1, 자기자신은 0을 대입한다.
    2. range는 각 선수들의 번호가 담겨 있는 배열이다. Array.from() 문법으로도 만들 수 있다.
    3. 이를 3중 순회하며, mid값이 a와 b 사이에 있는 경우 a와 b 사이의 승패관계가 성립될 수 있다. (플로이드 와샬 알고리즘)
    4. 다시, 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 어느 방법을 사용해도 무방하다. (결국은 완전탐색 알고리즘을 구현하는 것이 목적)

    다양한 방법들이 있지만, 나와 유사한 방법을 쓴 경우도 있었기에 이 방법을 권장하는 바이다.

     

    반응형
Designed by Tistory.