그리디 알고리즘 - 1이 될 때까지
문제 설명
어떠한 수 N이 1이 될 때까지 다음의 두 과정 중 하나를 반복적으로 선택하여 수행하려고 합니다.
단, 두 번째 연산은 N이 K로 나누어 떨어질 때만 선택할 수 있습니다.
- N에서 1을 뺍니다.
- N을 K로 나눕니다.
예를 들어 N이 17, K가 4라고 가정합시다. 이때 1번의 과정을 한 번 수행하면 N은 16이 됩니다.
이후에 2번의 과정을 두 번 수행하면 N은 1이 됩니다.
결과적으로 이 경우 전체 과정을 실행한 횟수는 3이 됩니다. 이는 N을 1로 만드는 최소 횟수입니다.
N과 K가 주어질 때 N이 1이 될 때까지 1번 혹은 2번의 과정을 수행해야 하는 최소 횟수를 구하는 프로그램을 작성하시오.
문제 해결 아이디어
주어진 N에 대하여 "최대한 많이 나누기"를 수행하면 됨.
- 1을 빼는 것 보다 2 이상으로 나누는 작업이 N을 훨씬 많이 줄일 수 있음.
- N = 25, K = 3일 때 예시.
정당성 분석
가능하면 최대한 많이 나누는 작업이 최적의 해를 항상 보장할 수 있을까?
: N이 아무리 큰 수여도, K로 계속 나눈다면 기하급수적으로 빠르게 줄일 수 있음.
다시 말해, K가 2이상이기만 하면, K로 나누는 것이 1을 빼는 것 보다 항상 빠르게 N을 줄일 수 있음.(최적의 해 성립)
복습 도중 틀린 부분
int(input().split())이라고 사용했는데
TypeError: int() argument must be a string, a bytes-like object or a number, not 'list'라는 에러가 돌아왔다.
int(input().split())가 아니라, map(int, input().split())임을 기억하자.
소스 코드
n, k = map(int, input().split())
result = 0
# N이 K이상이라면 K로 계속 나누기
while n >= k:
# N이 K로 나누어 떨어지지 않는다면, N에서 1씩 빼기
while n % k != 0:
n -= 1
result += 1
# K로 나누기
n //= k
result += 1
# 마지막으로 남은 수 에 대하여 1씩 빼기
while n > 1:
n -= 1
result += 1
print(result)
위 코드가 더 직관적으로 이해가 쉬움.문제에서 N의 범위가 10만 이하이므로, 위 처럼 문제 해결할 수 있음.
그러나, N이 100억 이상으로 큰 수가 되는 경우에 시간복잡도를 log로 줄이는 테크닉이 필요함.N이 K의 배수가 되도록 '효율적으로' 한 번에 빼는 방식으로 아래와 같이 작성 가능.
#n, k 공백 기준으로 구분하여 입력받기
n, k = map(int, input().split())
result = 0
while True:
# N이 K로 나누어 떨어지는 수가 될 때까지만 1씩 빼기
target = (n // k) * k # n이 k로 나누어 떨어지는 값 = target
result += (n - target) # n - target = 나머지, 즉 나머지를 result에 넣는다.
# 즉, 1을 빼는 연산을 몇 번 수행할 지에 대한 내용.
n = target # 1씩 뺀 값인 target으로 n을 최신화
# N이 K보다 작을 때 (더 이상 나눌 수 없을 때) 반복문 탈출
if n < k:
break
# k로 나누기
n //= k# n을 k로 나눈다.
result += 1
# 마지막으로 남은 수에 대하여 1씩 빼기
result += (n-1)
print(result)
나는 여기서 "# 마지막으로 남은수에 대하여 1씩 빼기" 이 부분이 너무 와닿지가 않았음.(이해가 안됬음)
'왜 남은 수에 대해서 1씩 빼는거지?' 그리고 '왜 저런식으로 빼지?' 에 대한 대답이 안나오는 상태였음.
그래서,
n=26, k=5라고 가정하고 손으로 시퀀스를 따라가보니까 이해가됨.
이 시퀀스의 결론은, result에 1이 한번 더 더해졌기 때문에 22번에서 1을 한번 빼주는 것이다.
while문이 if break문(14~15번)을 만나기 전에 7~11번을 한번 수행하게 짜여져있음.
그래서 7~11번을 수행한 결과, result에 +1이 되어짐.
그래서 22번에서 result에 -1을 한번 해주는 것임.
이 말이 이해가 안된다면
나 처럼 손으로 적으면서 while문을 느껴보면 바로 이해 될 것이다.
이코테 2021 시리즈 씹어먹기 by 조랭이떡
시리즈 목차
- 코딩테스트 출제 경향 및 알고리즘 성능 평가
- 파이썬 문법 부수기
- 그리디
- 구현
- DFS & BFS (추후링크)
- 정렬 알고리즘 (추후링크)
- 이진 탐색 (추후링크)
- 다이나믹 프로그래밍 (추후링크)
- 최단 경로 알고리즘 (추후링크)
- 기타 그래프 이론 (추후링크)
- 코딩 테스트에서 자주 출제되는 기타 알고리즘 (추후링크)
- 개발형 코딩 테스트 (추후링크)