2021. 4. 15. 07:31ㆍ프로그래밍/알고리즘
📚 그리디 (Greedy) 알고리즘
'탐욕법'이라고도 소개되는 이 알고리즘은 어떠한 문제가 있을 때 단순 무식하게, 탐욕적으로 문제를 푸는 알고리즘이다.
- 단순하지만 강력한 문제 해결 방법
- 매 순간 가장 좋아보이는 것을 선택. 현재의 선택이 나중에 미칠 여향에 대해서는 고려하지 않는다.
- 보통 코딩 테스트에서 출제되는 그리디 알고리즘 문제는 창의력, 문제를 풀기 위한 최소한의 아이디어를 떠올릴 수 있는 능력을 요구
📃 [예제 1] 거스름돈
- 풀이시간 : 15분
- 문제풀이
n = 1260
count = 0
# 큰 단위의 화폐부터 차례대로 확인
list = [500, 100, 50, 10]
for coin in list:
count += n // coin # 해당 화폐로 거슬러 줄 수 있는 동전의 개수 세기
n %= coin
print(count)
화폐의 종류만큼 반복을 수행 => 시간 복잡도 O(K)
이 알고리즘의 시간 복잡도는 동전의 총 종류에만 영향을 받고 거슬러 줘야 하는 금액의 크기와는 무관하다는 것을 알 수 있다.
- 정당성 확인
가지고 있는 동전 중에서 가장 큰 단위가 항상 작은 단위의 배수이므로 작은 단위의 동전들을 종합해 다른 해가 나올 수 없다.
만약 서로 배수형태가 아니라 무작위로 주어진 경우 그리디알고리즘으로 해결 X => 다이나믹 프로그래밍으로 해결
ex) 화폐단위가 500, 400, 100 이고 800원을 거슬러 줘야함.
- 그리디 알고리즘으로 풀었을 때 : 500원 + 100원 + 100원 + 100원
- 최적의 해 : 400원 + 400원
⚡ [문제 1] 큰 수의 법칙
- 풀이시간: 30분
- 문제풀이
n, m, k = map(int, input().split())
data = list(map(int, input().split()))
data.sort() # 입력받은 수들 정렬
first = data[n-1] # 가장 큰 수
second = data[n-2] # 두 번째로 큰 수
result = 0
while True:
for _ in range(k): # 가장 큰 수를 K번 더하기
if m == 0 : break
result += first
m -= 1
if m == 0 : break
result += second # 두 번째로 큰 수를 한 번 더하기
m -= 1
print(result)
입력값 중에서 가장 큰 수와 두 번째로 큰 수만 저장
'가장 큰 수를 K번 더하고 두 번째로 큰 수를 한번 더하는 연산'을 반복 수행
🙌 더 효율적인 문제 해결방법
M = 100억 이상이면 위의 소스코드는 시간 초과 판정
간단한 수학적 아이디어를 이용해 다음과 같이 문제를 더 효율적으로 해결할 수 있다.
예를 들어 N, M, K값과 입력값이 다음과 같을 때
- N = 5, M = 8, K = 3
- 입력값 = 2, 4, 5, 4, 6
- 가장 큰 수 : 6, 두 번째로 큰 수 : 5
정답은 (6 + 6 + 6 + 5) + (6 + 6 + 6 + 5) 으로 46이 된다.
- 우선 반복되는 수열에 대해서 파악 (6 + 6 + 6 + 5)
- 반복되는 수열의 길이는 (K + 1)
- M을 (K + 1) 로 나눈 몫이 반복되는 수열의 횟수 M / K+1 = 2
- 다시 여기에 K를 곱해주면 가장 큰 수가 등장하는 횟수 (M / K+1) * K = 6
- 만약 M이 (K + 1) 로 나누어떨어지지 않는 경우 (M % K+1) = 0
가장 큰 수가 더해지는 횟수(4번 + 5번의 합)
int(M / (K + 1)) * K + M % (K + 1)
최종 풀이
n, m, k = map(int, input().split())
data = list(map(int, input().split()))
data.sort()
first = data[n-1]
second = data[n-2]
result = 0
# 가장 큰 수가 더해지는 횟수 계산
max = int(m / (k + 1)) * k
max += m % (k + 1)
# 가장 큰 수 더하기 + 두 번째로 큰 수 더하기
result = (first * max) + (second * (m-max))
print(result)
⚡ [문제 2] 숫자 카드 게임
- 풀이시간: 10분
- 문제풀이
# 1. 나의 풀이
n,m = map(int,input().split())
result = 0;
for i in range(n):
data = list(map(int,input().split()))
data.sort() # 정렬해서 최소값을 찾는 방식
if data[0] > result:
result = data[0]
print(result)
# 2. 교재 풀이
n,m = map(int,input().split())
result = 0;
for i in range(n):
data = list(map(int,input().split()))
result = max(result, min(data)) # min(), max() 함수를 이용해서 찾는 방식
print(result)
⚡ [문제 3] 1이 될 때까지
- 풀이시간: 4분
- 문제풀이
# 1. 나의 풀이
n,k = map(int, input().split())
result = 0
while True:
if n == 1: break
if n % k == 0:
n //= k
result += 1
else :
n -= 1
result += 1
print(result)
# 2. 교재 풀이 (시간복잡도 O(NlogN))
n,k = map(int, input().split())
result = 0
while True:
# (N == K 로 나누어 떨어지는 수) 가 될 때까지 1씩 빼기
target = (n // k) * k
result += (n - target)
n = target
# N이 K보다 작을 때(더 이상 나눌 수 없을 때) 반복문 탈출
if n < k : break
# K로 나누기
result += 1
n //= k
# 마지막으로 남은 수에 대하여 1씩 빼기
result += (n-1)
print(result)
-정당성 확인
K가 2 이상이기만 하면 K로 나누는 것이 1을 빼는 것보다 항상 빠르게 N의 값을 줄일 수 있으며, N이 결국 1에 도달한다는 것을 알 수 있다.
🐾 정리
위의 세 문제 모두 어렵지 않은 문제였지만, 얼마나 논리적으로 생각해서 알고리즘을 구현하느냐에 따라 코드 차이가 많이 나는 것을 느꼈다.
마지막 문제 '1이 될 때까지' 는 N을 K로 나눌 수 있을 때와 없을 때를 if문을 사용하여 쉽게 구현했는데 이 소스코드는 N의 범위가 10만 이하이므로 빠르게 동작했지만, 만약 N이 100억 이상의 큰 수가 되는 경우를 가정했을 때는 교재풀이처럼 효율적으로 한 번에 빼는 방식으로 작성해줘야 통과했을 것이다.
회사에서도 새로운 프로젝트는 들어가기 전 담당한 파트를 설계할 때, 과장님께서 늘 논리적으로 생각하고 접근해야한다고 말씀하셨는데 컴퓨터를 사용해서 뭐든 하려고 하면 컴퓨터적인 사고, 즉 논리적인 사고를 하는 것이 진리인 것 같다.
※ 해당 포스팅은「이것이 취업을 위한 코딩테스트다(나동빈 저)」책 내용을 바탕으로 작성하였습니다.
'프로그래밍 > 알고리즘' 카테고리의 다른 글
[이것이 코딩테스트다] 구현 - 완전탐색과 시뮬레이션 (0) | 2021.04.18 |
---|---|
[이것이 코딩테스트다] 시간복잡도와 공간복잡도 (0) | 2021.04.18 |
[프로그래머스] [Level1] 정수 내림차순으로 배치하기 - Java (0) | 2021.04.07 |
[프로그래머스] [Level1] 제일 작은 수 제거하기 - Java (0) | 2021.01.08 |
[프로그래머스] [Level1] 자연수 뒤집어 배열로 만들기 - Java (0) | 2021.01.06 |