Note
그리디 알고리즘(Greedy Algorithm)을 활용하여 최대한 많은 회의를 선택하는 문제입니다.
문제
한 개의 회의실이 있는데 이를 사용하고자 하는 N개의 회의에 대하여 회의실 사용표를 만들려고 한다. 각 회의 I에 대해 시작시간과 끝나는 시간이 주어져 있고, 각 회의가 겹치지 않게 하면서 회의실을 사용할 수 있는 회의의 최대 개수를 찾아보자. 단, 회의는 한번 시작하면 중간에 중단될 수 없으며 한 회의가 끝나는 것과 동시에 다음 회의가 시작될 수 있다. 회의의 시작시간과 끝나는 시간이 같을 수도 있다. 이 경우에는 시작하자마자 끝나는 것으로 생각하면 된다.
문제 링크: https://www.acmicpc.net/problem/1931
입력
첫째 줄에 회의의 수 N(1 ≤ N ≤ 100,000)이 주어진다. 둘째 줄부터 N+1 줄까지 각 회의의 정보가 주어지는데 이것은 공백을 사이에 두고 회의의 시작시간과 끝나는 시간이 주어진다. 시작 시간과 끝나는 시간은 2³¹-1보다 작거나 같은 자연수 또는 0이다.
출력
첫째 줄에 최대 사용할 수 있는 회의의 최대 개수를 출력한다.
예제
예제 입력 1:
11
1 4
3 5
0 6
5 7
3 8
5 9
6 10
8 11
8 12
2 13
12 14
예제 출력 1:
4
힌트: (1,4), (5,7), (8,11), (12,14) 를 이용할 수 있다.
문제 분석
이 문제는 그리디 알고리즘을 사용하는 전형적인 문제입니다.
핵심 포인트
- 회의 선택: 겹치지 않는 회의를 최대한 많이 선택
- 그리디 전략: 종료 시간이 가장 빠른 회의부터 선택
- 정렬 기준: 종료 시간 기준 정렬 (같으면 시작 시간 기준)
왜 종료 시간 기준인가?
종료 시간이 빠른 회의를 선택하면:
- 더 많은 회의를 선택할 수 있는 기회가 생김
- 남은 시간이 최대화됨
해결 방법
그리디 알고리즘을 사용합니다:
- 회의를 종료 시간 기준으로 정렬 (같으면 시작 시간 기준)
- 첫 번째 회의를 선택
- 다음 회의 중 시작 시간이 이전 회의 종료 시간 이상인 것을 선택
- 반복
코드 구현
제공된 코드
import sys
readline = sys.stdin.readline
n = int(readline())
schedules: list[(int, int)] = []
for _ in range(n):
start, end = map(int, readline().rstrip().split())
schedules.append((start, end))
schedules.sort(key=lambda x: (x[1], x[0]))
result = 0
time = 0
for i in range(n):
# 시작시간이 현재 시간보다 늦으면 +1
if time <= schedules[i][0]:
result += 1
time = schedules[i][1]
print(result)코드 분석
1. 입력 처리
import sys
readline = sys.stdin.readline
n = int(readline())
schedules: list[(int, int)] = []
for _ in range(n):
start, end = map(int, readline().rstrip().split())
schedules.append((start, end))- 입력 최적화를 위해
sys.stdin.readline()사용 - 회의의 시작 시간과 종료 시간을 리스트에 저장
2. 정렬
schedules.sort(key=lambda x: (x[1], x[0]))정렬 기준:
- 첫 번째: 종료 시간 (
x[1]) - 오름차순 - 두 번째: 시작 시간 (
x[0]) - 오름차순
이유:
- 종료 시간이 빠른 회의를 우선 선택
- 종료 시간이 같으면 시작 시간이 빠른 것을 선택
3. 그리디 선택
result = 0
time = 0
for i in range(n):
# 시작시간이 현재 시간보다 늦으면 +1
if time <= schedules[i][0]:
result += 1
time = schedules[i][1]동작 과정:
time: 마지막으로 선택한 회의의 종료 시간- 현재 회의의 시작 시간이
time이상이면 선택 가능 - 선택하면
result증가,time업데이트
개선된 코드
더 명확하게 작성한다면:
import sys
readline = sys.stdin.readline
n = int(readline())
meetings = []
for _ in range(n):
start, end = map(int, readline().rstrip().split())
meetings.append((start, end))
# 종료 시간 기준 정렬 (같으면 시작 시간 기준)
meetings.sort(key=lambda x: (x[1], x[0]))
count = 0
last_end = 0
for start, end in meetings:
if start >= last_end:
count += 1
last_end = end
print(count)개선 사항:
- 변수명을 더 명확하게 (
schedules→meetings,time→last_end) - 튜플 언패킹 사용 (
start, end)
시간 및 공간 복잡도
시간 복잡도
- 정렬: O(n log n) - 회의 개수만큼
- 그리디 선택: O(n) - 한 번 순회
- 전체: O(n log n)
공간 복잡도
- 회의 리스트: O(n)
- 전체: O(n)
예제 동작 과정
예제 입력 1: n=11
정렬 전:
[(1, 4), (3, 5), (0, 6), (5, 7), (3, 8), (5, 9), (6, 10), (8, 11), (8, 12), (2, 13), (12, 14)]
정렬 후:
[(1, 4), (3, 5), (0, 6), (5, 7), (3, 8), (5, 9), (6, 10), (8, 11), (8, 12), (2, 13), (12, 14)]
그리디 선택 과정:
- (1, 4) 선택 →
last_end = 4,count = 1 - (3, 5) 스킵 (시작 시간 3 < 4)
- (0, 6) 스킵 (시작 시간 0 < 4)
- (5, 7) 선택 →
last_end = 7,count = 2 - (3, 8) 스킵 (시작 시간 3 < 7)
- (5, 9) 스킵 (시작 시간 5 < 7)
- (6, 10) 스킵 (시작 시간 6 < 7)
- (8, 11) 선택 →
last_end = 11,count = 3 - (8, 12) 스킵 (시작 시간 8 < 11)
- (2, 13) 스킵 (시작 시간 2 < 11)
- (12, 14) 선택 →
last_end = 14,count = 4
결과: 4개
그리디 알고리즘의 정당성
왜 이 방법이 최적해인가?
증명:
- 종료 시간이 가장 빠른 회의를 선택하지 않는다고 가정
- 다른 회의를 선택하면, 그 회의의 종료 시간이 더 늦음
- 따라서 더 적은 회의를 선택할 수 있음
- 따라서 종료 시간이 가장 빠른 회의를 선택하는 것이 최적
주의사항
1. 정렬 기준
정렬 기준이 매우 중요합니다:
- 종료 시간 기준 정렬이 핵심
- 종료 시간이 같으면 시작 시간 기준 정렬
2. 경계 조건
회의의 시작 시간과 종료 시간이 같을 수 있습니다:
start >= last_end조건으로 처리 가능
3. 입력 최적화
N이 최대 100,000이므로 input() 대신 sys.stdin.readline()을 사용해야 합니다.
관련 알고리즘
- 그리디 알고리즘 (Greedy Algorithm)
- Python 입력 최적화
- 정렬 (Sorting)