
문제 설명에서 P 의 조건이 여러개 주어서 헷갈릴 수 있지만 정리해보자면 P는 "0을 제외하고 연결되어있는 자연수" 라고 생각하면 된다.
예를 들어 k진수로 변환한 수가 "010042401"이라면 1, 424, 1이 소수인지 판별하면 된다.
function solution(n, k) {
let answer = 0;
const kFormationNum = n.toString(k);
for(let i=0; i<kFormationNum.length;){
while(kFormationNum[i] === '0') i++;
let j = i;
while(kFormationNum[j] !== '0' && j < kFormationNum.length) j++;
const p = kFormationNum.slice(i,j);
if(isPrimeNumber(+p)) answer++;
i = j;
}
return answer;
}
function isPrimeNumber(num){
if(num === 0 || num === 1) return false;
for(let i=2; i<=Math.sqrt(num); i++){
if(num % i === 0) return false;
}
return true;
}
"0을 제외하고 연결되어 있는 자연수"를 선택하기 위해 solution 함수에서 for 문을 돌아준다.
i는 0이 아닌 수의 시작지점에서 멈추고, j는 i에서부터 0이 등장할 때까지 1씩 증가한다. 그러면 j가 멈췄을 때, kFormationNum[i] ~ kFormationNum[j-1]은 "0을 제외하고 연결되어 있는 자연수" 즉, p가 된다.
또한 kFormationNum의 마지막 숫자가 0으로 끝나지 않는 경우 j가 무한히 증가할 수 있기 때문에 j < kFormationNum.length라는 조건도 걸어둔다.
p가 결정되었으면 p가 소수인지 판별하기 위해 isPrimeNumber 함수에 전달한다. isPrimeNumber 함수는 "하나의 수를 그 수의 제곱근 까지 1씩 증가시키며 나누어서 나누어 떨어지면 소수가 아니고, 나누어 떨어지는 수가 없으면 소수다." 라는 논리에 의거한 기본적인 소수 판별 알고리즘이다.
1은 소수가 아니기 때문에 num이 1일 경우에는 false를 리턴해준다. 여기서 조금 특별한 케이스는 num에 0이 들어오는 케이스다.
solution 함수에서 i와 j가 동일한 경우, p는 공백('')되는데 이것이 Number 타입으로 변환되어 0이 isPrimeNumber 함수에 전달된다. 그러면 반복문을 거치지 않고 true를 반환하기 때문에 잘못된 결과가 나온다.
이 예외 처리를 해주지 않으면 "테스트케이스 12번" 에서 틀리게 되므로 이러한 조건을 걸어주었다.
위의 풀이 설명에서도 말했듯이, 소수를 판별하는 함수에 0이 전달된 경우를 테스트하는 케이스로 추정된다.
그래서 num에 0이 전달되는 경우가 어떤 경우가 있을까 생각해봤다.
kFormationNum이 0으로 끝날 경우 i는 kFormationNum.length까지 증가하고 j또한 kFormationNum.length가 된다. 즉, i와 j가 같아지고, slice메서드는 start 보다 end-1이 더 작을 경우 공백('')을 반환한다. 따라서 p는 공백('')이 되고, 공백은 타입 변환에 의해 0이 된다.
이러한 경우 때문에 isPrimeNumber함수에 0이 전달된 경우 예외처리를 해주어야 한다.
에라토스테네스의 체를 이용해서 풀어보려고 했을 때 TestCase 1번과 11번에서 런타임 에러가 발생했다.
코드는 다음과 같다.
function solution(n, k) {
let answer = 0;
const kFormationNum = n.toString(k);
for (let i = 0; i < kFormationNum.length - 1; ) {
while (kFormationNum[i] === '0') i++;
let j = i;
while (kFormationNum[j] !== '0' && j < kFormationNum.length) j++;
const p = kFormationNum.slice(i, j);
if (isPrimeNumber(+p)) answer++;
i = j;
}
function isPrimeNumber(num) {
if (num === 0 || num === 1) return false;
const primeNumber = Array(num + 1).fill(true);
for (let i = 2; i <= Math.floor(Math.sqrt(num)); i++) {
if (primeNumber[i]) {
// i가 소수인 경우
let j = 2;
while (j * i <= num) {
primeNumber[j * i] = false;
j++;
}
}
}
return primeNumber[num];
}
return answer;
}
런타임 에러의 발생 원인은 isPrimeNumber함수에 을 초과하는 수가 전달되었을 때다.
예를 들어 797,161 를 3진수로 변환하면 1,111,111,111,111이 된다. 자바스크립트에서 Number 타입은 까지 안전하게 다룰 수 있기 때문에 신경쓰지 않고 있었지만 배열의 최대 길이를 생각하지 못하고 있었다.
자바스크립트에서 배열의 최대 길이는 플랫폼마다 다르지만 대략 인 것 같다.
이는 43억보다 조금 작은 수치이며 43억은 1,111,111,111,111보다 작기 때문에 RangeError: invalid array length 가 발생한 것이다.
그래서 에라토스테네스의 체 대신 다른 방법을 사용했다.
참고자료
MDN : RangeError: invalid array length
Programmers : 1번, 11번 테스트케이스의 반례 (코틀린 풀이)