프로그래머스 92335 - k진수에서 소수 개수 구하기 (Lv.5)
핵심 접근 — 0을 구분자로 토큰 분리 + 제곱근 소수 판정, 토큰은 long long
프로그래머스 92335 - k진수에서 소수 개수 구하기 (Lv.5)
출처: https://school.programmers.co.kr/learn/courses/30/lessons/92335
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// 프로그래머스 92335 - k진수에서 소수 개수 구하기 (Lv.5)
// https://school.programmers.co.kr/learn/courses/30/lessons/92335
// 문제 설명
// 양의 정수 n을 k진수로 바꿨을 때, 조건에 맞는 소수(P)의 개수를 반환하라.
// 조건은 0P0 / P0 / 0P / P 형태 — 즉 P 안에 0이 없고, 앞뒤가 0이거나 문자열 경계다.
// P는 k진수 표기를 그대로 10진수 수로 읽은 값으로 소수 판정한다.
// 제약 조건
// 1 <= n <= 1,000,000
// 3 <= k <= 10
// Example
// Input : n = 437674, k = 3
// Output: 3 (211020101011 → 211, 2, 1, 1, 11 → 소수는 211, 2, 11)
//
// Input : n = 110011, k = 10
// Output: 2 (110011 → 11, 11 → 둘 다 소수)
// 접근 — 진법 변환 + 0을 구분자로 토큰 분리 + 제곱근 소수 판정
// "앞뒤가 0 또는 경계"라는 조건은 결국 0을 구분자로 문자열을 자르는 것과 같다.
// 정규식이나 4가지 형태 분기 없이, 0에서 끊고 끊긴 조각만 소수 판정하면 된다.
// 1) n을 k진수 문자열로 변환 (나머지를 모아 뒤집는다).
// 2) 왼쪽부터 한 자리씩 cur = cur * 10 + digit 으로 누적하고, 0이나 끝을 만나면 토큰 확정.
// 3) 토큰을 sqrt까지만 나눠 소수 판정.
// 자리 수는 k = 3일 때 최대 13자리(3^12 <= 10^6 < 3^13)이므로 토큰 값이 최대 약 1.1 x 10^12.
// int로 받으면 오버플로 — cur은 long long이어야 한다.
// 시간 O(log_k n * sqrt(최대 토큰)), 공간 O(log_k n)
#include <string>
#include <algorithm>
using namespace std;
// n을 k진수 문자열로 변환
string toBase(int n, int k) {
string s;
while (n > 0) { s += char('0' + n % k); n /= k; }
reverse(s.begin(), s.end()); // 나머지는 낮은 자리부터 나오므로 뒤집는다
return s;
}
bool isPrime(long long v) {
if (v < 2) return false; // 1은 소수가 아니다 — 토큰 "1"이 자주 나온다
for (long long d = 2; d * d <= v; d++) // sqrt까지만 확인
if (v % d == 0) return false;
return true;
}
int solution(int n, int k) {
string s = toBase(n, k);
int answer = 0;
long long cur = 0; // 진행 중인 토큰의 10진수 값
// i == s.size() 를 마지막 구분자처럼 취급 — 끝에 0을 붙이는 특수 처리가 사라진다
for (int i = 0; i <= (int)s.size(); i++) {
if (i == (int)s.size() || s[i] == '0') {
if (cur > 0 && isPrime(cur)) answer++;
cur = 0; // 연속된 0은 cur == 0 이라 그냥 건너뛴다
} else {
cur = cur * 10 + (s[i] - '0');
}
}
return answer;
}
정리
- 문제가 준
0P0/P0/0P/P네 가지 형태는 패턴 매칭이 아니라 “0으로 자르기” 한 문장으로 접힌다. 네 형태를 그대로 조건문으로 옮기면 경계(문자열 시작·끝)에서 인덱스 처리가 갈라지는데, “0을 구분자로 보고 토큰을 모은다”로 바꾸면 형태 구분 자체가 사라진다. 문제 설명의 표현을 그대로 코드로 번역하지 말고 같은 뜻의 더 단순한 표현을 먼저 찾는다. - 루프를
i <= s.size()로 한 칸 더 돌린 게 두 번째 단순화다. 마지막 토큰만 루프 밖에서 따로 확정하는 코드는 같은 판정 로직이 두 곳에 복제되는데, 문자열 끝을 가상의 구분자로 취급하면 확정 로직이 한 군데로 모인다. 연속된 0도cur == 0으로 자연히 걸러진다. - 오버플로가 이 문제의 실제 함정이다.
n <= 10^6만 보고 int로 받으면 틀린다. k = 3이 최악으로,3^12 = 531,441 <= 10^6 < 3^13이라 최대 13자리가 나온다. 실제로 n = 797,161은 3진수로1111111111111이고 토큰을 10진수로 읽으면 약 1.1 x 10^12 — int 상한(약 2.1 x 10^9)의 500배다. 제약이 작아 보여도 변환 후 값의 상한을 따로 계산하는 습관이 필요하다(위장 42578에서 곱의 누적 상한을 먼저 잡은 것과 같은 점검). - 소수 판정은
d * d <= v로 제곱근까지만 본다. 토큰 상한 1.1 x 10^12의 제곱근은 약 1.05 x 10^6이고 토큰 개수는 많아도 7개 수준이라 전체 연산이 10^7 안쪽 — Lv.5 효율성 기준에서 여유가 있다. 여기서d <= v / 2같은 절반 순회로 쓰면 10^12 스케일이 되어 그대로 시간 초과다. 반대로 에라토스테네스 체는 상한이 10^12여서 메모리가 감당되지 않으므로, 후보가 몇 개뿐일 때는 체가 아니라 개별 시행 나눗셈이 맞다. isPrime의v < 2가드가 정답을 가른다. 3진수 표기에는 토큰1이 흔하게 등장하고(예제 1에도 두 개), 1을 소수로 세면 3이 아니라 5가 나온다. 1은 소수가 아니다를 판정 함수 첫 줄에 박아두면 호출부에서 매번 신경 쓸 일이 없다.- 검증: 예제 2개(3, 2)와 경계 케이스(n = 1 → 0, n = 2 → 1), 13자리 오버플로 케이스(n = 797,161) 통과 (MSVC
/std:c++17컴파일·실행). 커리큘럼 95번.
핵심 요약 —
0P0·P0·0P·P네 형태는 “0을 구분자로 토큰 자르기” 한 줄로 접히고, 문자열 끝을 가상의 구분자로 취급하면 마지막 토큰 특수 처리도 사라진다. 진짜 함정은n <= 10^6이 아니라 변환 후 토큰 값 — k = 3에서 13자리(약 1.1 x 10^12)까지 커지므로 int가 아니라 long long이어야 한다.
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.