코플릿/toyproblem

toy problem 01

테오구 2021. 10. 20. 01:31
728x90

orderOfPresentation

문제:

말썽꾸러기 김코딩은 오늘도 장난을 치다가 조별 발표 순서가 담긴 통을 쏟고 말았습니다.

선생님께서는 미리 모든 발표 순서의 경우의 수를 저장해 놓았지만 김코딩의 버릇을 고치기 위해 문제를 내겠다고 말씀하셨습니다.

김코딩은 모든 조별 발표 순서에 대한 경우의 수를 차례대로 구한 뒤 발표 순서를 말하면 이 발표 순서가 몇 번째 경우의 수인지를 대답해야 합니다.

총 조의 수 N과 선생님이 말씀하시는 발표 순서 k가 주어질 때, 김코딩이 정답을 말 할 수 있게 올바른 리턴 값을 구하세요.

모든 경우의 수가 담긴 배열은 번호가 작을수록 앞에 위치한다고 가정합니다. ex) N = 3일경우, [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

 

let output = orderOfPresentation(3, [2, 3, 1]);
console.log(output); // 3

output = orderOfPresentation(5, [1, 3, 2, 4, 5])
console.log(output); // 6

 

문제의 접근

이 문제는 경우의 수 그중 순열과 관련된 문제이다.

발표를 정하는 순서는 총 N!이다.

팩토리얼은 N의 값이 높아질 경우 기하급수적으로 늘어난다. 

따라서 모든 배열의 경우의 수를 구하지 않고 몇 번째 경우의 수인지 구하는 로직을 고려해야 된다.

ex) N = 4의 총 경우의 수는 4! = 24가지이다. 이중 [4 ,1, 2, 3]이 몇 번째인지 알 수 있다.

1 + 3 X 3!번째이다.

앞자리가 정해져 있는 상태에서 나머지를 줄세우는 경우의 수는 (N-1)!이기 때문이다.
그리고 그것이 세 번 반복되기 때문에 3을 곱해주는 것이다.
그리고 그 다음의 경우의 수이기 때문에 1을 더하면 된다.

 

알고리즘

  1. 우선 factorial 값을 구하는 함수를 재귀 or 반복문으로 만든다.
  2. order 변수를 선언하고 0으로 초기화한다. order변수를 최종적으로 출력할 것이다.
  3. N개의 조 중에 어떠한 조가 이미 포함되었는지 확인하기 위해 isUsed 배열을 생성하고 모든 값을 False로 초기화한다.
  4. K의 개수만큼 배열을 반복시킨다.
    1. 우선 num 변수를 선언하고 K의 i번째 인덱스의 값으로 준다.
    2. isUsed의 num번째 인덱스의 값을 true로 바꾼다.
    3. isUsed 배열을 1번째 인덱스부터(0번째 아님) num번째 인덱스 전까지 복사한 다음, 배열의 길이를 구한다.
    4. 그리고 4-3값에 (N-i-1)!을 곱한다.
    5. 4에서 구한 값과 order에서 구한 값을 더한다.
  5. order 값을 리턴한다.

코드

더보기
function orderOfPresentation(N, K) {
  // 조의 개수 N, 발표 순서 K

  // N은 최대 12입니다.
  // 발표 순서를 만드는 것은 순열(permutation)이므로, 발표 순서의 최대 크기는 12!입니다.
  // 이는 약 4억 8천만에 해당하며, 일반적인 수행 속도 상한(약 1억)을 훨씬 상회하므로 순열을 전부 생성하는 것은 올바른 접근 방법이 아닙니다.

  const factorial = (n) => {
    if (n <= 1) return 1;
    return n * factorial(n - 1);
  };

  // 발표 순서를 담는 변수 생성
  let order = 0;
  
  // N개의 조 중에, 어떠한 조가 이미 포함되었는지 확인하기 위해 배열을 생성합니다.
  // 만약 N이 3이라면 [false, false, false, false]로 생성됩니다.
  // 제일 첫 번째는 더미 데이터입니다. (인덱스는 0부터 시작하지만 조는 1부터 시작하기 때문에)
  const isUsed = Array(N + 1).fill(false);
  
  // K의 길이만큼 순회합니다.
  for (let i = 0; i < K.length; i++) {
    // K의 i번째 조를 변수에 담습니다.
    const num = K[i];
    // 사용했는지 판별하기 위해 isUsed에 체크합니다. (중복이 아니기 때문에)
    isUsed[num] = true;
    // num보다 앞에 올 수 있는 수들의 배열을 복제해서,
    const candidates = isUsed.slice(1, num);
    // 이 중에서 아직 사용되지 않은 수의 개수를 구합니다.
    const validCnt = candidates.filter((el) => el === false).length;
    // 아직 사용되지 않은 수, 그 전까지의 모든 경우의 수를 카운트합니다.
    const formerCnt = validCnt * factorial(N - i - 1);
    // order에 추가합니다.
    order = order + formerCnt;

    /**
     * 설명을 덧붙이자면,
     * 만약 K가 [2, 3, 1]이라고 가정했을 때, 첫 번째 num은 2가 될 것입니다.
     * 2가 제일 앞에 있다고 가정한다면, 앞자리가 2의 차례가 오기 전에 1의 모든 경우의 수를 구했을 것이고,
     * 1의 모든 경우의 수를 지금부터 구하게 됩니다.
     * 
     * 그렇다면, IsUsed 배열은 이렇게 됩니다. [false(더미), false, true, false]
     * candidates 배열은 이렇게 됩니다. => [false]
     * validCnt는 이렇게 됩니다. => 1
     * formerCnt는 이렇게 됩니다. => 1 * factorial(3 - 0 - 1) // i는 0부터 시작하기 때문에 N에서 남아 있는 수를 구할 때 - 1이 추가로 필요합니다.
     * order는 2를 추가합니다.
     * 
     * 두 번째를 순회했을 땐, num이 3이 됩니다.
     * 그렇다면, IsUsed 배열은 이렇게 됩니다. [false(더미), false, true, true]
     * candidates 배열은 이렇게 됩니다. => [false]
     * validCnt는 이렇게 됩니다 => 1
     * formerCnt는 이렇게 됩니다 => 1 * factorial(3 - 1 - 1)
     * order는 1을 추가합니다. (3)
     * 
     * 세 번째를 순회했을 땐, num이 1이 됩니다.
     * IsUsed 배열은 이렇게 됩니다. [false, true, true, true]
     * candidates 배열은 []이고, validCnt는 0이 되어, formerCnt는 0이 됩니다.
     * 
     * 발표 순서는 0부터 시작하기 때문에 0, 1, 2, 3으로
     * 결과적으로, 값은 3이 됩니다.
     */
  }
  
  return order;
}
728x90

'코플릿 > toyproblem' 카테고리의 다른 글

toy problem 14  (0) 2021.10.26
toy problem 04  (0) 2021.10.21
toy problem 03  (0) 2021.10.20
toy problem 02  (0) 2021.10.20
toy problem 09  (0) 2021.10.19