본문 바로가기

Algorithm

그리디 알고리즘(Greedy)과 구현(Implementation) (1)

그리디 알고리즘(탐욕법)

- 현재 상황에서 지금 당장 좋은 것만 고르는 방법을 의미
- 최적의 해를 구할 수 있는지 검토
- 탐욕법으로 얻은 해가 최적의 해가 되는 상황에서, 이를 추론할 수 있어야 문제를 풀 수 있음

문제 1 ) 거스름 돈

출처 : 이코테


문제해결 방법

- 최적의 해를 빠르게 구하기 위해선 가장 큰 화폐 단위부터 돈을 거슬러 주면 됨
- N원을 거슬러 줘야 할 때, 가장 먼저 500원으로 거슬러 줄 수 있을 만큼 거슬러 줌
이후, 100원, 50원, 10원을 차례대로 거슬러 줌

* 정당성 분석

가장 큰 화폐 단위부터 돈을 거슬러 주는 것이 최적의 해를 보장하는 이유
- 가지고 있는 동전 중에서 큰 단위가 항상 작은 단위의 배수이므로 작은 단위의 동전들을 종합해 다른 해가 나올 수 없음
if ) 800원을 거슬러 주어야 하는데 화폐 단위가 500원, 400원, 100원일 경우, 위의 상황을 적용시킬 수 없음
- 그리디 알고리즘 문제에서는 문제 풀이를 위한 최소한의 아이디어를 떠올리고 이것이 정당한지 검토하는 것이 중요

money = [500, 100, 50, 10]
n = 1260
count = 0

for i in range(len(money)):
  count += n // money[i]
  n %= money[i]

print(count)

* 시간 복잡도 분석(거스름 돈)

- 화폐의 종류가 K라고 할 때, 소스코드의 시간 복잡도는 O(K) : 화폐의 종류 만큼만 반복
- 알고리즘의 시간 복잡도는 거슬러줘야 하는 금액과는 무관, 동전의 총 종류에만 영향

문제 2 ) 1이 될 때까지

출처 : 이코테

문제해결 방법

- 주어진 N에 대하여 최대한 많이 나누기를 수행
- N의 값을 줄일 때 2 이상의 수로 나누는 작업이 1을 빼는 작업보다 수를 훨씬 빠르게 줄일 수 있음

* 정당성 분석

가능하면 최대한 많이 나누는 작업이 최적의 해를 항상 보장?
- N이 아무리 큰 수여도, K로 계속 나눈다면 기하급수적으로 빠르게 줄일 수 있음
- K가 2 이상이기만 하면, K로 나누는 것이 1을 빼는 것보다 항상 빠르게 N을 줄일 수 있음 -> N은 항상 1에 도달(최적의 해 성립)


(내 풀이)

n, k = map(int, input().split())
count = 0

while True:
  # N이 1일 때 반복문 탈출
  if n == 1:
    break    
    
  # N이 K와 나누어 떨어질 수 있을 때까지 빼기
  if n % k != 0:
    n -= 1
    count += 1
    
  # K로 나누기
  else:
    n //= k
    count += 1
    
print(count)


(이코테 풀이)

- 반복문이 한 번 반복이 될 때마다 N이 K로 나누어지는 연산이 수행 -> N이 빠르게 줄어들음
- 시간 복잡도가 O(nlogn) 복잡도가 나올 수 있음 -> 빨리 실행될 수 있음 (N이 매우 큰 수라도 빠르게 해결 가능)

n, k = map(int, input().split())
result = 0

while True:
	# N이 K로 나누어 떨어지는 수가 될 때까지 빼기
    # N이 K로 나누어 떨어지는 가장 가까운 수 찾기
	target = (n // k) * k
    result += (n - target)
    n = target
    
    # N이 K보다 적을 때(더 이상 나눌 수 없을 때) 반복문 탈출
    if n < k:
    	break
    
    # K로 나누기
    result += 1
    n //= k

# N이 1보다 큰 수가 나왔다면
# 마지막으로 남은 수에 대하여 1씩 빼기
result += (n-1)
print(result)


문제 3 ) 곱하기 혹은 더하기

출처 : 이코테


문제해결 방법

- '+' 보다는 'x'가 더 값을 크게 만듦
- 두 수 중에서 하나라도 '0' 혹은 '1'인 경우, 곱하기보다는 더하기를 수행하는 것이 효율적
- 두 수에 대하여 연산을 수행할 때, 두 수 중 하나라도 1이하인 경우에는 더하며, 두 수가 모두 2이상인 경우에는 곱하기


(내 풀이)

s = list(map(int, input()))
answer = s[0]

for i in range(1, len(s)):
  if answer == 0 or s[i] == 0 or s[i] == 1:
    answer += s[i]
    continue
  answer *= s[i]
  
print(answer)


(이코테 풀이)

data = input()
result = int(data[0])

for i in range(1, len(data)):
	num = int(data[i])
    if num <= 1 or result <= 1:
    	result += num
    else:
    	result *= num
        
print(result)


문제 4 ) 모험가 길드

출처 : 이코테


문제해결 방법

- 오름차순으로 정렬
- 앞에서부터 공포도를 하나씩 확인 -> 현재 그룹에 포함된 모험가의 수가 현재 확인하고 있는 공포도보다 크거나 같다면 이를 그룹 설정
- 오름차순으로 정렬 -> 항상 최소한의 모험가만의 수만 포함하여 그룹을 결성하게 됨


(내 풀이)
- 불필요한 배열 선언으로 메모리 차지...

n = int(input())
people = list(map(int, input().split()))
team = 0
team_person = []

# 공포도가 낮은 모험가부터 오름차순 정렬
people = sorted(people)

for i in range(len(people)):
  # 팀 그룹에 사람 한 명 씩 넣기
  team_person.append(people[i])

  # 만약 해당 팀의 사람의 수가 공포도보다 높거나 같다면
  if len(team_person) >= people[i]:
    # 팀 형성
    team += 1
    # 팀 그룹 초기화
    team_person = []
  else: 
    continue

print(team)

(이코테 풀이)

n = int(input())
data = list(map(int, input().split()))
data.sort()

result = 0
count = 0

# 공포도를 낮은 것부터 하나씩 확인하며
for i in data:
	# 현재 그룹에 해당 모험가를 포함시키기
	count += 1
    
    # 현재 그룹에 포함된 모험가의 수가 현재의 공포도 이상이라면, 그룹 결성
    if count >= i
    	# 총 그룹의 수 증가
    	result += 1
        # 현재 그룹에 포함된 모험가의 수 초기화
        count = 0
        
 print(result)