본문 바로가기
파이썬/코딩테스트

[파이썬] 코딩테스트 17 기출_ 소수 구하기 문제

by nemonemonemo 2025. 8. 11.

문제

https://school.programmers.co.kr/learn/courses/30/lessons/92335?language=python3

 

프로그래머스

SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프

programmers.co.kr


문제 설명

양의 정수 n이 주어집니다. 이 숫자를 k진수로 바꿨을 때, 변환된 수 안에 아래 조건에 맞는 소수(Prime number)가 몇 개인지 알아보려 합니다.

  • 0P0처럼 소수 양쪽에 0이 있는 경우
  • P0처럼 소수 오른쪽에만 0이 있고 왼쪽에는 아무것도 없는 경우
  • 0P처럼 소수 왼쪽에만 0이 있고 오른쪽에는 아무것도 없는 경우
  • P처럼 소수 양쪽에 아무것도 없는 경우
  • 단, P는 각 자릿수에 0을 포함하지 않는 소수입니다.
    • 예를 들어, 101은 P가 될 수 없습니다.

예를 들어, 437674을 3진수로 바꾸면 211020101011입니다. 여기서 찾을 수 있는 조건에 맞는 소수는 왼쪽부터 순서대로 211, 2, 11이 있으며, 총 3개입니다. (211, 2, 11을 k진법으로 보았을 때가 아닌, 10진법으로 보았을 때 소수여야 한다는 점에 주의합니다.) 211은 P0 형태에서 찾을 수 있으며, 2는 0P0에서, 11은 0P에서 찾을 수 있습니다.

정수 n과 k가 매개변수로 주어집니다. n을 k진수로 바꿨을 때, 변환된 수 안에서 찾을 수 있는 위 조건에 맞는 소수의 개수를 return 하도록 solution 함수를 완성해 주세요.


제한사항

  • 1 ≤ n ≤ 1,000,000
  • 3 ≤ k ≤ 10

입출력 예

n k result

437674 3 3
110011 10 2

입출력 예 설명

입출력 예 #1

문제 예시와 같습니다.

입출력 예 #2

110011을 10진수로 바꾸면 110011입니다. 여기서 찾을 수 있는 조건에 맞는 소수는 11, 11 2개입니다. 이와 같이, 중복되는 소수를 발견하더라도 모두 따로 세어야 합니다.


문제 요약

주어진 자연수 n을 k진수로 바꾼 뒤, 0을 구분자로 잘라 나온 덩어리(연속된 숫자들)를 10진수로 바꿔 소수인 것의 개수를 세는 문제이다.

예: n = 437674, k = 3 → n을 3진수로 바꾸면 "211020101011" → "0" 기준으로 자르면 ["211","2","1","1","11"] → 각 덩어리를 소수 판정 → 소수 개수 반환.


한 줄 풀이 아이디어

  1. n을 k진수 문자열로 변환 →
  2. "0"으로 split →
  3. 빈 문자열과 1을 걸러낸 뒤 각 숫자를 효율적으로 소수 판정 → 카운트 반환.

Step 1 — k진법으로 변환하는 함수

def convert_k(n,k):
    # 나머지들을 문자열로 기록할 변수 --> 생성!!!
    k_sys = ""# 개취 : 리스트로,,,기타 등등,,,,,++0은 패스..등등...개취~~~
    while n > 0 :
        k_sys += str(n%k) #나머지 추가-> list였다면 append나 insert 썼어도 됨
        n = n//k #n값 갱신
    #-> k_sys = "110101020112"
    #원하는 결과는 -> 위의 결과를 역순으로 바꾼 것 -> 211020101011
    k_sys = k_sys[::-1]
    return k_sys
  • def convert_k(n,k):
    : 함수 convert_k를 정의한다. 입력으로 정수 n과 진법 k를 받는다. 이 함수는 n을 k진법 문자열로 바꿔서 반환할 것이다.
  • k_sys = ""
    : 변환 결과를 문자로 붙여갈 변수 k_sys를 빈 문자열로 만든다. (여기서는 문자열로 이어붙이는 방식을 썼다.)
  • while n > 0 :
    : n이 0보다 클 동안 반복한다. 진법 변환은 n을 계속 k로 나눠 몫이 0이 될 때까지 수행한다.
  • k_sys += str(n%k)
    : n을 k로 나눈 나머지(n % k)를 문자열로 바꿔 k_sys 뒤에 붙인다. 주의: 이렇게 하면 나머지들이 뒤에서 앞으로 쌓인다. 그래서 뒤에서 설명하는 [::-1]로 뒤집는다. (효율 개선을 원하면 리스트에 append 후 join을 권장함)
  • n = n//k
    : n을 k로 정수 나눈 몫으로 갱신한다. 다음 반복에서 이 몫에 대해 다시 나머지를 구한다.
  • k_sys = k_sys[::-1]
    : 파이썬에서 [::-1]은 문자열을 뒤집는 슬라이싱 표기다. 따라서 지금까지 쌓은 나머지들을 올바른 순서로 뒤집어준다.
  • return k_sys
    : 완성된 k진법 문자열을 반환한다.

Step1 테스트

#test1
n= 437674
k=3
convert_k(n,k) #211020101011
#test2
n=110011
k=10
convert_k(n,k) #110011
  • convert_k(437674, 3)의 결과 주석 #211020101011은 실제로 위 함수가 반환하는 문자열 예시다.
  • k=10일 때는 10진수 그대로이므로 변환 결과가 원래 숫자 문자열과 같다.

Step 2 — 0을 기준으로 분리

n= 437674
k=3
print(convert_k(n,k))
temp = convert_k(n,k).split("0")
temp
#211020101011
#['211', '2', '1', '1', '11']
  • print(convert_k(n,k))
    : 변환 결과를 화면에 출력한다.
  • temp = convert_k(n,k).split("0")
    : 변환된 문자열을 '0'을 기준으로 나눠서 리스트를 만든다. '0'은 소수 판정에서 끊는 경계다.
n=110011
k=10
print(convert_k(n,k))
temp = convert_k(n,k).split("0")
temp
#110011
#['11', '', '11'] ##++처리하는 과정에서 빈 문자열을 걸러내야겠다
  • 두 번째 예시에서 ['11', '', '11']처럼 빈 문자열이 나올 수 있다. 이는 ...00...처럼 0이 연속되거나 문자열의 시작/끝에 0이 있을 때 생긴다. 따라서 빈 문자열을 걸러야 한다.
  • 아래와 같이 빈 문자열을 건너뛰면서 각 덩어리에 대해 소수 판정을 하면 된다:
for i in temp:
    if i !="":
        print(i)#소수인지 판별
    else:
        pass
#11
#11
  • 위 루프는 빈 덩어리를 무시하고 실제 숫자 덩어리만 처리한다.

Step 3 — 소수 판별 함수

def check_prime(m):
    c=2 #정의대로 2부터 주구장창 나누자
    if m ==1:
        return False
    esle: # 입력 수가 2 이상인 수들만
        while c < m : # 2부터 m-1까지 계속 나눠가면서
            if m % c == 0:
                return False
            else:
                c += 1
    return  True #2부터 입력 m보다 1 작은 m-1까지 한 번도 나눠 떨어지지 않음 
  • def check_prime(m):
    : 정수 m이 소수인지 아닌지를 판단하는 함수다.
  • c=2
    : 소수 정의대로 2부터 나누어 본다.
  • if m ==1: / return False
    : 1은 소수가 아니므로 False를 반환한다.
  • else:
    : 2 이상일 때 다음 단계로 간다.
  • while c < m :
    : c를 2에서 m-1까지 하나씩 늘리며 m % c == 0인지 확인한다. 나눠떨어지면 합성수이므로 False 반환.
  • return True
    : 위 반복에서 한 번도 나눠떨어지지 않으면 m은 소수이므로 True 반환.

문제점(효율성)

  • 위 방식은 2부터 m-1까지 모두 검사하므로 시간이 많이 걸린다. m이 크면 매우 비효율적이다. (코테에서는 시간초과 가능성이 높다.)
  • 개선: 대칭성 때문에 √m 까지만 검사하면 된다. 즉 while c*c <= m: 혹은 while c <= int(m**0.5): 방식으로 바꾸면 시간 복잡도를 크게 줄일 수 있다. 또한 2 이후에는 짝수는 건너뛰고 홀수만 검사하면 절반으로 더 줄일 수 있다.

위의 코드들 조립

def solution(n,k):
    answer = 0 # <- 기본 양식은 -1 인데, 딱히 갯수로 리턴하라고 해서 0으로 세팅
    #1)입력 n에 대해서 k진법으로 변환 -> 문자열
    #2) n_k_str: 0을 기준으로 분리 -> 리스트
    #3) 2번의 리스트 원소들을 롤링하면서 -> for
    #    --> 소수인지 체크!!!
    #        소수라면 +1 카운팅!!!!
    
    return answer

조립한 전체 코드

def convert_k(n, k):
    k_sys = ""# 개취 : 리스트로,,,기타 등등,,,,,++0은 패스..등등...개취~~~
    while n > 0: #
        k_sys += str( n % k) # 나머지 추가... // 리스트 append, insert(0,~) etc
        n = n // k # 나눌 수에 대한 갱신!! --> 몫!!
    k_sys = k_sys[::-1]
    return k_sys

def check_prime(m):
    c = 2 # --> 정의대로 2부터 주구 장창 나누자~~~
    if m == 1:
        return False
    else: # --> 입력 수가 2이상인 수들만,,,,
        while c < m:# 2부터 m-1까지 계속 나눠가면서,,,,,,,,,,,
            if m % c ==0:
                return False
            else:
                c += 1
    return True # 2부터 입력m보다 1작은 m-1까지 한 번도 나눠떨어지 않음!!생존자!

def solution(n, k):
    answer = 0
    # 1) n --> k
    n_to_k = convert_k(n,k) # "211020101011"
    # 2) 0을 기준으로 분리!!!! + 덩어리 롤링!!
    for num in n_to_k.split("0"): # ["211","2","1","1","11"]
        if num =="":
            continue
        else: # --> 빈 문자 덩어리가 아닌 경우 --> 소수 판정!!!
            if check_prime( int(num) )==True:
                answer+=1 # 카운팅!!!
    return answer

한 줄씩 설명 (핵심만 정리)

  • n_to_k = convert_k(n,k)
    : n을 k진법 문자열로 받는다.
  • for num in n_to_k.split("0"):
    : '0'을 기준으로 덩어리를 반복한다.
  • if num == "":
                 continue
    : 빈 문자열은 건너뛴다.
  • if check_prime( int(num) )==True:
    : 덩어리를 정수로 바꿔 소수 판정(참이면 카운트 증가).
  • 마지막에 return answer로 소수의 개수를 반환한다.

효율성 개선 코드

def convert_k(n,k):
    # 나머지들을 문자열로 기록할 변수 --> 생성!!!
    k_sys = ""# 개취 : 리스트로,,,기타 등등,,,,,++0은 패스..등등...개취~~~
    while n > 0 :
        k_sys += str(n%k) #나머지 추가-> list였다면 append나 insert 썼어도 됨
        n = n//k #n값 갱신
    #-> k_sys = "110101020112"
    #원하는 결과는 -> 위의 결과를 역순으로 바꾼 것 -> 211020101011
    k_sys = k_sys[::-1]
    return k_sys

def check_prime(m):
    c=2 #정의대로 2부터 주구장창 나누자
    if m ==1:
        return False
    if m==2:
        return True
    else: # 입력 수가 2 이상인 수들만
        c=2
        end = int(m**0.5) + 1 # --> o(m) ---> o(m**1/2)
        while c < end : # 2부터 m-1까지 계속 나눠가면서
            if m % c == 0:
                return False
            else:
                c += 1
    return True #2부터 입력 m보다 1 작은 m-1까지 한 번도 나눠 떨어지지 않음 

def solution(n, k):
    answer = 0
    # 1) n --> k
    n_to_k = convert_k(n,k) # "211020101011"
    # 2) 0을 기준으로 분리!!!! + 덩어리 롤링!!
    for num in n_to_k.split("0"): # ["211","2","1","1","11"]
        if num =="":
            continue
        else: #-> 빈 문자 덩어리가 아닌 경우 -> 소수 판정함
            if check_prime(int(num)) == True:
                answer+=1 #카운팅

    return answer

check_prime(m) 함수

  • def check_prime(m):
    : 정수 m이 소수인지 아닌지를 판단하는 함수다. 참/거짓(True/False)을 반환한다.
  • c=2
    : 검사에 사용할 나눗셈 변수 c를 2로 초기화한다. (이 초기화는 이후 c=2로 다시 되어 중복이다.)
  • if m ==1: / return False
    : 1은 소수가 아니므로 즉시 False를 반환한다.
  • if m==2: / return True
    : 2는 소수이므로 즉시 True를 반환한다.
  • else:
    : m이 2보다 클 때 실행되는 블록이다.
  • c=2
    : 다시 c를 2로 초기화한다. (앞서 이미 했으므로 하나는 지워도 된다.)
  • end = int(m**0.5) + 1
    : m의 제곱근을 구해서 정수로 만든 뒤 1을 더한다. 소수 판정은 sqrt(m)까지만 검사해도 충분하므로 이 값을 상한으로 잡는다. 예: m=49이면 end=int(7)+1=8이다.
  • while c < end :
    : c가 end-1까지 반복하도록 한다. 즉 c = 2, 3, ..., int(sqrt(m))를 검사한다. 이 범위만 검사하면 충분하다.
  • if m % c == 0: / return False
    : c로 나누어떨어지면 합성수이므로 False를 반환한다.
  • else: / c += 1
    : 나누어떨어지지 않으면 c를 1 증가시켜 다음 후보로 검사한다.
  • return True
    : 루프를 모두 통과하면 m에는 2~sqrt(m) 사이에서 나눠떨어지는 수가 없으므로 m은 소수다.