[java] 프로그래머스 - 줄 서는 방법
업데이트:
문제
프로그래머스-줄 서는 방법
n명이 사람이 일렬로 줄을 설 때 가능한 방법을 사전 순으로 나열했을 때 k번째 방법을 구하는 문제
-
제한 사항
줄을 서는 사람의 수 n :n
<= 20
방법을 사전 순으로 나열했을 때 구할 순서 k :k
<= n! -
예시
n = 3 일때 가능한 방법은 6가지다
사전 순서대로 나열한다면- [1, 2, 3]
- [1, 3, 2]
- [2, 1, 3]
- [2, 3, 1]
- [3, 1, 2]
- [3, 2, 1]
k = 5일때 k번째 줄 서는 방법은 [3, 1, 2] 이다
풀이
ㅠㅠ 수학에 자신없는 나에겐 힘겨웠던 문제였다
n
이 20이하이므로 전체 방법을 다 구하려면 시간이 터진다
가능한 방법은 n!이므로 아무리 k까지 연산을 돌린다고 해도 k
가 n!이하
이기 때문에 ❌❌
시행착오(효율성 시간초과)
처음 생각한 방법은 k를 이용해서 가장 앞의 원소를 구한다음, 그 이후부터 세어 나가는 방법이다
n = 3
일 때 숫자 1이 제일 앞에 오는 경우의 수는 2번이다
n = 4
일 때 숫자 1이 제일 앞에 오는 경우의 수는 6번이다
즉, n개의 원소를 나열하는 방법의 수 중에서 한 숫자가 가장 앞에 오는 경우의 수는 (n-1)!
이다
이 (n-1)!
와 k
를 가지고 첫번째 원소와 그 원소 이후 몇번째를 구해야하는지 알 수 있다
f = (n-1)!
시작 원소 = k % f == 0? k / f : k / f + 1
이후 순서 = k % f == 0? f : k % f
- 예시
n = 3, k = 5 // [3, 1, 2]
f = (n-1)! = 2! = 2
시작원소 = 5 % 2 == 0 ? 2 :3
이후 순서 = 5 % 2 == 0 ? 2 :1
- [3, _, _] 에서 남은 [1, 2]를 배치할때 사전 순
1
번째 => [3, 1, 2]
- [3, _, _] 에서 남은 [1, 2]를 배치할때 사전 순
하지만 이 방법도 n = 20이라면 남은 19자리를 배치해야해서 효율성에서 처참하게 시간이 터졌다
찐 풀이
위에서 했던 시행착오방법을 이용해서 남은 자리들도 같은 방식으로 맞춰나갔다
위의 시행착오는 첫번째 자리만 찾는 거였는데 이걸 다음자리에 적용하고 그 다음자리 .. 이런식으로 찾는다
long last = k % fact == 0 ? fact : k % fact; // 이후(n-1)의 k
long first = k % fact == 0 ? k / fact : k / fact + 1; // 시작원소
int cnt = 0; // 몇번쨰인지를 세기 위함
for (int i = 0; i < chk.length; i++) {
if (!chk[i]) {
cnt++;
if (cnt == first) {
answer[idx++] = i + 1; // 현재 자리에 들어올 값
chk[i] = true;
getNumber(n - 1, last, chk, fact / (n - 1));
}
}
}
시행착오와 다른점은 first(시작원소)의 경우 숫자를 그자체가 아닌,
사용하지 않은 원소들 중 몇번째인지 라는 것이다
또한 (n-1)!를 계속 구하는 것이 아니라, 처음에 구한 값을 나눠가면서 적용한다
idx-1까지 왔다면 하나남은 원소를 마지막 인덱스에 넣어주고 종료한다
- 주의사항
last(n-1에게 넘겨줄 k)
는 long형의 k의 연산결과이므로 long을 해줘야한다
소스코드
import java.util.*;
class Solution {
static long N;
static int[] answer;
static int idx;
public int[] solution(int n, long k) {
answer = new int[n];
// factorial을 n으로 나눈 수, (n-1)! => 한 숫자가 가장앞에오는 경우의 수
boolean[] chk = new boolean[n];
N = n - 1;
idx = 0;
getNumber(n, k, chk, fac(n - 1));
return answer;
}
private void getNumber(int n, long k, boolean[] chk, long fact) {
if (idx == N) {
for (int i = 0; i < chk.length; i++) {
if (!chk[i]) {
answer[idx] = i + 1;
break;
}
}
return;
}
long last = k % fact == 0 ? fact : k % fact;
long first = k % fact == 0 ? k / fact : k / fact + 1;
int cnt = 0;
for (int i = 0; i < chk.length; i++) {
if (!chk[i]) {
cnt++;
if (cnt == first) {
answer[idx++] = i + 1;
chk[i] = true;
getNumber(n - 1, last, chk, fact / (n - 1));
}
}
}
}
public long fac(int n) {
long i = 1;
for (int j = 2; j < n + 1; j++) {
i *= j;
}
return i;
}
}
댓글남기기