개념
그리디는 지금 당장 가장 좋아 보이는 걸 집는 방식이에요. 뷔페에서 눈앞의 제일 맛있어 보이는 접시를 바로 집는 것과 같아요. 문제는, 지금 집은 게 나중에 후회를 만들지 않는다는 걸 보장할 수 있어야 한다는 거예요. 후회가 없다고 확신이 서면 그냥 집으면 되고(짧고 빠른 코드), 확신이 안 서면 모든 경우를 따지는 DP로 가야 해요. 그래서 그리디의 진짜 공부는 코드가 아니라 '이 선택이 후회 없는가'를 판단하는 눈이에요.그리디는 매 순간 가장 좋아 보이는 것을 고르고 뒤돌아보지 않는 방식이에요. 코드가 짧고 빨라요. 문제는 언제 이게 옳은가이고, 그것을 판단하는 것이 이번 주의 전부예요.말은 어렵지만 실전에서 확인하는 방법은 단순해요. 반례를 먼저 찾아보는 것이에요. 5분 안에 반례가 안 나오면 그리디로 밀어붙이고, 반례가 나오면 DP로 가요. 증명하려 들면 시험 시간이 사라져요.
회의실 배정에서 '시작이 빠른 것'이나 '짧은 것'을 고르면 왜 안 되는지 직접 반례를 그려 봐요. 긴 회의 하나가 짧은 회의 둘을 막는 그림이 바로 나와요. 이런 식으로 기준을 바꿔가며 반례를 찾는 연습이 그리디의 훈련법이에요.
그리디가 통하려면
- 탐욕 선택 속성 — 지금의 최선을 골라도 최적해를 놓치지 않는다
- 최적 부분 구조 — 그 선택을 하고 남은 문제의 최적해를 더하면 전체 최적해가 된다
자주 나오는 형태
| 문제 | 무엇을 기준으로 정렬 | 왜 |
|---|---|---|
| 회의실 배정 | 끝나는 시각 오름차순 | 빨리 끝날수록 뒤에 남는 시간이 많다 |
| 구명보트 | 무게 오름차순 + 양끝 포인터 | 가장 무거운 사람은 가장 가벼운 사람과 짝지어야 이득 |
| 동전 거스름돈 (배수 화폐) | 큰 단위부터 | 큰 단위가 작은 단위의 배수라 손해가 없다 |
| 최소 회의실 개수 | 시작 시각 + 힙 | 가장 빨리 끝나는 방을 재사용 |
직접 따라가 보기: 회의실 배정
회의 네 개가 있어요. 겹치지 않게 최대한 많이 잡는 게 목표예요. 끝나는 시각이 빠른 순으로 먼저 정렬해요. 빨리 끝날수록 뒤에 남는 시간이 많으니까요. 그다음 앞에서부터 하나씩 보면서, 마지막에 고른 회의가 끝난 시각(lastEnd) 이후에 시작하는 회의만 골라요.끝나는 시각 오름차순으로 정렬한 뒤, 안 겹치는 것만 골라요정렬 후 (끝나는 시각 기준)A 1 ~ 3B 2 ~ 5C 4 ~ 7D 6 ~ 8lastEnd = -inf (아직 아무것도 안 골랐어요)A 시작 1 >= -inf ? yes -> 고름 lastEnd = 3B 시작 2 >= 3 ? no -> 건너뜀C 시작 4 >= 3 ? yes -> 고름 lastEnd = 7D 시작 6 >= 7 ? no -> 건너뜀고른 회의 = A, C -> 최대 2개
B를 건너뛴 게 손해처럼 보여도, B를 골랐다면 lastEnd가 5가 돼서 C도 D도 못 잡아 오히려 하나만 남아요. 가장 빨리 끝나는 걸 고르는 선택이 뒤에 가장 넓은 자리를 남긴다는 게 이 그리디가 후회 없는 이유예요.그리디가 깨지는 순간
이번 주의 마지막 문제가 이 지점이에요. 동전이 1, 5, 10처럼 배수 관계면 큰 것부터 집는 그리디가 맞아요. 그런데 동전이 1, 3, 4이고 6원을 만든다면?- 그리디: 4 + 1 + 1 = 3개
- 최적: 3 + 3 = 2개
즉 그리디로 안 되는 문제란 '지금의 선택이 나중에 후회를 만드는' 문제예요. 후회를 없애려면 모든 선택지를 다 따져봐야 하는데, 그것을 중복 없이 하는 방법이 동적 계획법이에요.
패턴 코드
정렬 후 하나씩 집기 (회의실 배정)끝나는 시각 기준 정렬이 핵심. 시작 시각 기준으로 하면 틀려요.
javascriptCopy codemeetings.sort((a, b) => a[1] - b[1]);let count = 0;let lastEnd = -Infinity;for (const [start, end] of meetings) {if (start >= lastEnd) {count++;lastEnd = end;}}
정렬 후 양끝에서 짝짓기 (구명보트)
javascriptCopy codepeople.sort((a, b) => a - b);let i = 0;let j = people.length - 1;let boats = 0;while (i <= j) {if (people[i] + people[j] <= limit) i++;j--;boats++;}
이번 주 문제
이번 주 진행0 / 4
셀프 체크
다음 주로 넘어가기 전에 소리 내어 답해 보세요. 막히면 그 부분이 아직 덜 익은 개념이에요.- 회의실 배정에서 '시작이 빠른 순'으로 고르면 안 되는 반례를 그릴 수 있는가?
- 동전 1, 3, 4로 6원을 만들 때 그리디가 왜 지는가?
- 그리디를 쓸지 DP를 쓸지 판단할 때 스스로에게 던지는 질문은 무엇인가?
- 회의실 배정에서 가장 빨리 끝나는 회의를 고르면 왜 뒤에 자리가 가장 많이 남나요?