그리디 & 구현
[2021 이코테] 2. 그리디 & 구현
그리디 알고리즘
- 그리디 알고리즘(탐욕법)은 현재 상황에서 지금 당장 좋은 것만 고르는 방법을 의미
- 일반적인 그리디 알고리즘은 문제를 풀기 위한 최소한의 아이디어를 떠올릴 수 있는 능력을 요구
- 그리디 해법은 그 정당성 분석이 중요
- 단순히 가장 좋아 보이는 것을 반복적으로 선택해도 최적의 해를 구할 수 있는지 검토
- 일반적인 상황에서 그리디 알고리즘은 최적의 해를 보장할 수 없을 때가 많음
- 하지만 코딩 테스트에서의 대부분의 그리디 문제는 탐욕법으로 얻은 해가 최적의 해가 되는 상황에서, 이를 추론할 수 있어야 풀리도록 출제
문제: 거스름 돈

문제 해결 아이디어
- 최적의 해를 빠르게 구하기 위해서는 가장 큰 화폐 단위부터 돈을 거슬러 주면 됨
- N원을 거슬러 줘야 할 때, 가장 먼저 500원으로 거슬러 줄 수 있을 만큼 거슬러 줌
- 이후에 100원, 50원, 10원짜리 동전을 차례대로 거슬러 줄 수 있을 만큼 거슬러 주면 됨
- 가장 큰 화폐 단위부터 돈을 거슬러 주는 것이 최적의 해를 보장하는 이유는?
- 가지고 있는 동전 중에서 큰 단위가 항상 작은 단위의 배수이므로 작은 단위의 동전들을 종합해 다른 해가 나올 수 없기 때문
n = 1260
count = 0
array = [500, 100, 50, 10]
for coin in array:
count += n // coin
n %= coin
print(count)
for coin in array:
count += n // coin
n %= coin
문제: 1이 될 때까지

문제 해결 아이디어
- 주어진 N에 대하여 최대한 많이 나누기를 수행하면 됨
- N의 값을 줄일 때 2 이상의 수로 나누는 작업이 1을 빼는 작업보다 수를 훨씬 많이 줄일 수 있음
- 가능하면 최대한 많이 나누는 작업이 최적의 해를 항상 보장할 수 있을까?
- N이 아무리 큰 수여도, K로 계속 나눈다면 기하급수적으로 빠르게 줄일 수 있음
- 다시 말해 K가 2 이상이기만 하면, K로 나누는 것이 1을 빼는 것보다 항상 빠르게 N을 줄일 수 있음
- 또한 N은 항상 1에 도달하게 됨 (최적의 해 성립)
n, k = map(int, input().split())
result = 0
while True:
target = (n // k) * k
result += (n - target)
n = target
if n < k:
break
result += 1
n //= k
result += (n - 1)
print(result)
while True:
target = (n // k) * k
result += (n - target)
문제: 곱하기 혹은 더하기

문제 해결 아이디어
- 대부분의 경우 '+'보다는 'x'가 더 값을 크게 만듬
- 예를 들어 5 + 6 = 11이고, 5 x 6 = 30
- 다만 두 수 중에서 하나라도 '0' 혹은 '1'인 경우, 곱하기보다는 더하기를 수행하는 것이 효율적
- 따라서 두 수에 대하여 연산을 수행할 때, 두 수 중에서 하나라도 1 이하인 경우에는 더하며, 두 수가 모두 2 이상인 경우에는 곱하면 정답
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)
문제: 모험가 길드

문제 해결 아이디어
- 오름차순 정렬 이후에 공포도가 가장 낮은 모험가부터 하나씩 확인
- 앞에서부터 공포도를 하나씩 확인하며 '현재 그룹에 포함된 모험가의 수'가 '현재 확인하고 있는 공포도'보다 크거나 같다면 이를 그룹으로 설정
- 이러한 방법을 이용하면 공포도가 오름차순으로 정렬되어 있다는 점에서, 항상 최소한의 모험가의 수만 포함하여 그룹을 결성하게 됨
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)
result += 1
count = 0
구현 (Implementation)
- 구현이란, 머릿속에 있는 알고리즘을 소스코드로 바꾸는 과정
- 흔히 알고리즘 대회에서 구현 유형의 문제란 무엇을 의미할까?
- 풀이를 떠올리는 것은 쉽지만 소스코드로 옮기기 어려운 문제를 지칭
- 구현 유형의 예시
- 알고리즘은 간단한데 코드가 지나칠 만큼 길어지는 문제
- 실수 연산을 다루고, 특정 소수점 자리까지 출력해야 하는 문제
- 문자열을 특정한 기준에 따라서 끊어 처리해야 하는 문제
- 적절한 라이브러리를 찾아서 사용해야 하는 문제
- 일반적으로 알고리즘 문제에서 2차원의 공간은 행렬(Matrix)의 의미로 사용

- 시뮬레이션 및 완전 탐색 문제에서는 2차원 공간에서의 방향 벡터가 자주 활용

문제: 상하좌우

문제 해결 아이디어
- 이 문제는 요구사항대로 충실히 구현하면 되는 문제
- 일련의 명령에 따라서 개체를 차례대로 이동시킨다는 점에서 시뮬레이션(Simulation) 유형으로도 분류되며 구현이 중요한 대표적인 문제 유형
- 다만, 알고리즘 교재나 문제 풀이 사이트에 따라서 다르게 일컬을 수 있으므로, 코딩 테스트에서의 시뮬레이션 유형, 구현 유형, 완전 탐색 유형은 서로 유사한 점이 많다는 정도로만 기억
n = int(input())
x, y = 1, 1
plans = input().split()
dx = [0, 0, -1, 1]
dy = [-1, 1, 0, 0]
move_types = ["L", "R", "U", "D"]
for plan in plans:
for i in range(len(move_types)):
if plan == move_types[i]:
nx = x + dx[i]
ny = y + dy[i]
if nx < 1 or ny < 1 or nx > n or ny > n:
continue
x, y = nx, ny
print(x, y)
dx = [0, 0, -1, 1]
dy = [-1, 1, 0, 0]
move_types = ["L", "R", "U", "D"]
if plan == move_types[i]:
nx = x + dx[i]
ny = y + dy[i]
문제: 시각

문제 해결 아이디어
- 이 문제는 가능한 모든 시각의 경우를 하나씩 모두 세서 풀 수 있는 문제
- 하루는 86,400초이므로, 00시 00분 00초부터 23시 59분 59초까지의 모든 경우는 86,400가지
- 따라서 단순히 시각을 1씩 증가시키면서 3이 하나라도 포함되어 있는지를 확인하면 됨
- 이러한 유형은 완전 탐색(Brute Forcing) 문제 유형이라고 불림
- 가능한 경우의 수를 모두 검사해보는 탐색 방법을 의미
h = int(input())
count = 0
for i in range(h + 1):
for j in range(60):
for k in range(60):
if '3' in str(i) + str(j) + str(k):
count += 1
print(count)
if '3' in str(i) + str(j) + str(k):
count += 1
문제: 왕실의 나이트

문제 해결 아이디어
- 요구사항대로 충실히 구현하면 되는 문제
- 나이트의 8가지 경로를 하나씩 확인하며 각 위치로 이동이 가능한지 확인
- 리스트를 이용하여 8가지 방향에 대한 방향 벡터를 정의
input_data = input()
row = int(input_data[1])
column = int(ord(input_data[0])) - int(ord('a')) + 1
steps = [(-2, -1), (-1, -2), (1, -2), (2, -1), (2, 1), (1, 2), (-1, 2), (-2, 1)]
result = 0
for step in steps:
next_row = row + step[0]
next_column = column + step[1]
if next_row >= 1 and next_row <= 8 and next_column >= 1 and next_column <= 8:
result += 1
print(result)
column = int(ord(input_data[0])) - int(ord('a')) + 1
steps = [(-2, -1), (-1, -2), (1, -2), (2, -1), (2, 1), (1, 2), (-1, 2), (-2, 1)]
for step in steps:
next_row = row + step[0]
next_column = column + step[1]
문제: 문자열 재정렬

문제 해결 아이디어
- 요구사항대로 충실히 구현하면 되는 문제
- 문자열이 입력되었을 때 문자를 하나씩 확인
- 숫자인 경우 따로 합계를 계산
- 알파벳의 경우 별도의 리스트에 저장
- 결과적으로 리스트에 저장된 알파벳을 정렬해 출력하고, 합계를 뒤에 붙여 출력하면 정답
data = input()
result = []
value = 0
for x in data:
if x.isalpha():
result.append(x)
else:
value += int(x)
result.sort()
if value != 0:
result.append(str(value))
print(''.join(result))
if x.isalpha():
result.append(x)
print(''.join(result))