[묘공단] 코딩테스트 스터디 13주차
이 글은 골든래빗 코딩 테스트 합격자 되기 파이썬 편의 16장 문제풀이입니다
16. 그리디 개념
그리디 -> 문제 해결 과정에서 순간마다 최선의 선택을 하며 선택을 번복하지 않는 알고리즘
다시 말해서 지역 최적해를 추구한다라고 말할 수 있다
그리디 알고리즘이 최적해를 보장하려면
- 최적 부분 구조 (Optimal substructure): 부분해를 푸는 과정이 최적해를 구하는 과정과 일치
- 그리디 선택 속성 (Greddy selection property): 선택 과정이 다른 과정에 영향을 주지 않음
이런 특징때문에 항상 최적해를 구할 수 있다는 보장은 못하지만, 빠르게 근사해를 제공하는 효과는 누릴 수가 있다
앞에서 공부했던 Union-Find를 활용하여 최소 신장 트리를 구하는 알고리즘도 그리디 알고리즘의 일종
짐을 쪼갤 수 있는 배낭 문제도 흔히 알려진 그리디 알고리즘을 활용할 수 있는 예제 중 하나
16-4. 몸풀기 문제
거스름돈 주기
사용할 수 있는 코인이 서로 배수 관계이므로, 큰 숫자의 코인을 쓰는 경우 가짓수가 최소임이 보장됨
즉, 최적 부분 구조를 만족하며, 마찬가지로 그리디 선택 속성을 만족
따라서 큰 코인부터 사용할 갯수를 그리디하게 찾아서 채워나가는 식으로 풀이
from typing import List
def solution(amount: int) -> List[int]:
answer = []
current_amount = amount
for coin in [100, 50, 10, 1]:
num_of_coins, remainder = divmod(current_amount, coin)
answer.extend([coin] * num_of_coins)
current_amount = remainder
return answer
assert solution(123) == [100, 10, 10, 1, 1, 1]
assert solution(350) == [100, 100, 100, 50]
부분 배낭 문제
0/1 배낭이 아니라 부분 배낭이므로, 무게 당 가치를 계산하여 정렬한 후 그 순서대로 weight_limit까지 채워넣으면서 풀이
import math
from typing import List
def solution(items: List[List[int]], weight_limit: int) -> float:
sorted_items = sorted(items, key=lambda item: item[1] / item[0], reverse=True)
current_weight_limit = weight_limit
answer = 0.0
for weight, cost in sorted_items:
if weight <= current_weight_limit:
answer += cost
current_weight_limit -= weight
else:
answer += current_weight_limit * (cost / weight)
break
return round(answer, 2)
assert math.isclose(solution([[10, 19], [7, 10], [6, 10]], 15), 27.33)
assert math.isclose(solution([[10, 60], [20, 100], [30, 120]], 50), 240)
16-5. 실전 문제
예산
최대한 많은 부서를 지원해야하므로, 신청한 금액이 작은 순서대로 지원하면 결국 최적해가 된다
from typing import List
def solution(d: List[int], budget: int) -> int:
d.sort()
answer = 0
remained_budget = budget
for amount in d:
if amount <= remained_budget:
answer += 1
remained_budget -= amount
else:
break
return answer
assert solution([1, 3, 2, 5, 4], 9) == 3
assert solution([2, 2, 3, 3], 10) == 4
구명보트
최대한 2명씩 태워야 최소한의 보트를 탑승하는 것이므로, 가장 무거운 사람과 가장 가벼운 사람을 페어가 가능한 형태로
그리디하게 접근, 정렬을 한 후 left와 right를 적절하게 pointer를 갱신해주면 된다
from typing import List
def solution(people: List[int], limit: int) -> int:
people.sort()
left, right = 0, len(people) - 1
answer = 0
while left <= right:
if people[left] + people[right] <= limit:
left += 1
right -= 1
answer += 1
return answer
assert solution([70, 50, 80, 50], 100) == 3
assert solution([70, 80, 50], 100) == 3
귤 고르기
종류를 최소화 하는 것이 목적이므로, 사이즈별로 빈도를 세고 빈도가 낮은 사이즈부터 걸러내는 식으로 그리디하게 접근
바꿔 생각하면 특정 사이즈에 빈도가 많다면 골라냈을 때 이득이 없다는 것을 캐치하면 쉽게 풀이 가능하다
import collections
from typing import Sequence
def solution(k: int, tangerine: Sequence[int]) -> int:
count_by_size = collections.Counter(tangerine)
sorted_sizes = sorted(count_by_size.keys(), key=lambda x: count_by_size[x])
num_of_tangerines = len(tangerine) - k
for size in sorted_sizes:
if count_by_size[size] > num_of_tangerines:
break
num_of_tangerines -= count_by_size[size]
count_by_size.pop(size)
return len(count_by_size)
assert solution(6, [1, 3, 2, 5, 4, 5, 2, 3]) == 3
assert solution(4, [1, 3, 2, 5, 4, 5, 2, 3]) == 2
assert solution(2, [1, 1, 1, 1, 2, 2, 2, 3]) == 1
기지국 설치
정확성은 다 통과하지만 효율성에서 통과되지 않는 풀이
from typing import Sequence
def solution(n: int, stations: Sequence[int], w: int) -> int:
coverages = [False] * n
for station in stations:
start = max(0, station - 1 - w)
end = min(station -1 + w, n - 1)
for index in range(start, end + 1):
coverages[index] = True
answer = 0
temp_container = []
for index in range(n):
if coverages[index]:
if temp_container:
temp_container = []
answer += 1
else:
if len(temp_container) < 2 * w + 1:
temp_container.append(index)
else:
temp_container = [index]
answer += 1
return answer + 1 if temp_container else answer
assert solution(11, [4, 11], 1) == 3
assert solution(16, [9], 2) == 3
1부터 순서대로 이동하면서 해당 좌표가 설치된 기지국의 바운더리에 있는 지 없는 지 확인하면서 좌표를 업데이트
해당 좌표가 기지국 바운더리에 없으면 loc - w < loc < loc + w를 커버할 수 있도록 하고 좌표를 업데이트
해당 좌표가 기지국 바운더리에 있다면, 기지국이 설치된 위치 + w + 1로 좌표를 업데이트하면 빠짐 없이 커버가 가능해진다
from typing import Sequence
def solution(n: int, stations: Sequence[int], w: int) -> int:
location = 1
answer = 0
station_index = 0
while location <= n:
if station_index < len(stations) and location >= stations[station_index] - w:
location = stations[station_index] + w + 1
station_index += 1
else:
location += 2 * w + 1
answer += 1
return answer
assert solution(16, [9], 2) == 3
assert solution(11, [4, 11], 1) == 3