인터벌 스케줄링: 가장 먼저 끝나는 일정을 고르는 이유

·설명 약 2분·작성: 해비·블로그 소개

겹치지 않는 일정을 가장 많이 고를 때 왜 먼저 끝나는 일정을 선택하나요?

같은 자원 하나에서 겹치지 않는 일정을 가장 많이 고를 때에는, 가능한 일정 중 가장 빨리 끝나는 것을 선택합니다. 일찍 비워진 시간은 이후 일정을 받아들일 여지를 줄이지 않습니다.

적용 조건·범위: 일정 N개, 비교 정렬 기준 시간 O(N log N), 입력 복사와 결과 저장 공간 O(N)입니다. 종료 순 정렬을 이미 보장하면 선택 순회만 O(N)입니다. 양의 길이인 반열린 구간, 자원 하나, 동일한 일정 가치에서 선택 개수를 최대화합니다.

본문 근거: Competitive Programmer’s Handbook — 6.2 Scheduling

읽기 전에 알아두면 좋아요

아래 개념이 익숙하면 본문을 바로 읽으세요. 낯선 개념만 링크에서 복습하면 됩니다.

개념과 예제는 실행하지 않아도 읽을 수 있습니다. 내 컴퓨터·터미널에서 직접 실행하는 과정은 선택 실습입니다.

과정 위치와 선택 복습 안내

과정 위치: 그리디 선택과 반례 · 알고리즘 개념

과정 위치는 이 글이 속한 묶음입니다. 이 단계의 모든 개념을 먼저 읽을 필요는 없습니다. 위에 연결한 개념 중 낯선 내용만 복습하세요. 문제의 조건·상태·갱신 순서는 익숙한 언어로 설명하면 됩니다.

JavaScript로 문법·실행 복습하기 (선택)

개념 글의 코드는 JavaScript 예시입니다. 문제 화면에서는 JavaScript·Python·Java·C++·C# 중 익숙한 언어의 함수를 작성하세요. 본문의 Node.js 입출력 예시는 로컬 실행을 위한 별도 안내입니다.

인터벌 스케줄링: 가장 먼저 끝나는 일정을 고르는 이유 주제를 표현한 밝은 일러스트와 작은 곰 표지

같은 자원 하나에서 겹치지 않는 일정을 가장 많이 고를 때에는, 가능한 일정 중 가장 빨리 끝나는 것을 선택합니다. 일찍 비워진 시간은 이후 일정을 받아들일 여지를 줄이지 않습니다.

선수 지식: 배열 정렬·순회·탐욕 선택과 반례

  • 종료 시각으로 정렬한 뒤 선택·건너뛰기
  • 맞닿은 구간과 겹친 구간 구분하기
  • 일정 개수 최대화의 교환 논증 설명하기

최대화하는 값과 구간의 경계를 먼저 정합니다

인터벌 스케줄링은 시작과 종료 시각이 정해진 후보들 중 서로 겹치지 않는 일정을 선택하는 문제입니다. 이 글의 목표는 선택한 일정의 개수를 최대로 만드는 것입니다. 일정의 수익, 중요도, 총 진행 시간은 점수에 포함하지 않습니다. 일정마다 수익이 다르고 수익 합을 최대화한다면 종료 시각만으로 고르는 규칙은 최적성을 보장하지 않습니다. 목표가 무엇인지 고정해야 선택 규칙을 판단할 수 있습니다.

앞 일정 [1,4)의 종료와 다음 일정 [4,7)의 시작이 같은 시각임을 비교한 그림
[시작, 종료) 구간에서는 직전 종료와 다음 시작이 같아도 함께 선택할 수 있습니다. 원본 보기 (새 탭)

구간은 [start,end)로 해석합니다. 시작 시각은 포함하고 종료 시각은 포함하지 않으므로 [1,4)와 [4,7)은 함께 고를 수 있습니다. 코드에서는 다음 일정의 시작이 마지막 선택의 종료 이상인지 비교합니다. 모든 일정은 유한한 수 시각을 가지고 시작이 종료보다 작아야 합니다. 길이가 0인 구간은 이 예제에서 받지 않습니다. 빈 목록의 답은 빈 목록이며, 첫 일정을 찾기 위한 별도 가짜 시간값도 필요하지 않습니다.

종료 순서로 보고 가능한 일정만 남깁니다

후보가 A=[0,6), B=[1,4), C=[3,6), E=[4,7), H=[8,11)이라고 하겠습니다. 종료 시각으로 정렬하면 B가 가장 앞에 옵니다. B를 선택한 뒤에는 시작이 4보다 작은 후보를 건너뛰고 E를 선택합니다. 이어서 H를 선택하므로 결과는 B, E, H 세 개입니다. 시작이 가장 빠른 A부터 고르면 B와 E를 모두 놓치므로 시작 순서가 같은 의미를 갖지 않습니다.

종료 시각 순서 B,A,C,E,H를 검사해 B,E,H를 선택하고 직전 종료를 갱신하는 표
선택한 일정만 직전 종료 시각을 바꿉니다. 원본 보기 (새 탭)
종료 시각 우선 선택
후보 판단 마지막 종료 선택 목록
B=[1,4) 첫 일정 선택 4 B
A=[0,6) 0<4이므로 제외 4 B
C=[3,6) 3<4이므로 제외 4 B
E=[4,7) 4≥4이므로 선택 7 B, E
H=[8,11) 8≥7이므로 선택 11 B, E, H

후보를 건너뛸 때 마지막 종료를 갱신하지 않는 점도 중요합니다. 마지막 종료는 지금 본 후보의 종료가 아니라 실제 선택한 일정의 종료입니다. 제외된 긴 일정으로 이 값을 덮어쓰면 뒤의 유효한 일정을 부당하게 놓칠 수 있습니다.

최적해의 첫 일정을 바꾸어도 개수가 줄지 않습니다

최대로 많은 일정을 고른 어떤 해의 첫 일정을 X, 전체 후보 중 가장 빨리 끝나는 일정을 G라고 하겠습니다. G의 종료는 X의 종료보다 늦지 않습니다. 그 해에서 X를 G로 바꾸어도 X 뒤에 가능했던 일정들은 여전히 가능합니다. 뒤 일정의 시작은 X의 종료 이상이었고, G는 그보다 늦게 끝나지 않기 때문입니다. 따라서 G를 포함하는 최적해도 존재합니다.

최적해 첫 일정 X를 더 일찍 끝나는 G로 바꾸어 이후 일정을 유지하는 도식
가장 먼저 끝나는 일정으로 교체해도 뒤의 일정을 그대로 선택할 수 있습니다. 원본 보기 (새 탭)

G를 고른 뒤에는 G의 종료 이전에 시작하는 후보를 제외하고 같은 문제를 반복합니다. 매 단계의 선택이 남은 최적해의 첫 일정을 대신할 수 있으므로 선택 수의 최적성이 이어집니다. “짧은 일정이 더 많이 들어갈 것 같다”는 직감과 구분하세요. 길이가 가장 짧은 구간도 가운데를 차지해 양쪽의 두 일정을 동시에 막을 수 있습니다. 이 증명에서 필요한 비교는 일정 길이가 아니라 종료 시각입니다.

동률과 원본 순서의 계약을 분명히 합니다

종료 시각이 같은 후보는 시작 시각, 마지막으로 입력 위치를 비교하여 처리 순서를 정합니다. 종료 동률의 어느 후보를 고르더라도 이후에 남기는 시간의 경계는 같지만, 선택된 ID를 재현하려면 동률 규칙이 필요합니다. 이 예제는 원본 목록을 복사해 정렬하므로 화면이나 다른 계산에서 쓰는 입력 순서를 변경하지 않습니다. 반환 목록에는 선택한 일정 객체가 들어가며 객체 자체도 변경하지 않습니다.

종료·시작·입력 순서의 동률 비교 기준과 정렬용 배열 복사
동률 기준을 정해 결과를 재현하고 원본 배열을 보존합니다. 원본 보기 (새 탭)

후보 N개를 정렬한 뒤 한 번 순회하므로 일반적인 비교 정렬 기준 시간은 O(N log N), 복사·선택 결과를 위한 공간은 O(N)입니다. 이미 종료 시각 순으로 정렬되어 있고 그 계약을 믿을 수 있다면 선택 단계만 O(N)입니다. 학습 중에는 작은 후보 집합의 모든 부분집합을 열거하여 최대 개수를 비교해 보세요. 다만 같은 최대 개수라도 여러 선택 목록이 가능하므로, 다른 목록이라는 이유만으로 오답이라 판단하지 말고 겹침 여부와 개수를 먼저 확인해야 합니다.

전체 코드와 직접 실행

개념을 읽었다면 아래 예제를 직접 실행하며 상태 변화를 확인할 수 있습니다.

전체 JavaScript 코드와 실행 방법 펼치기

별도 패키지는 필요하지 않습니다. Node.js가 처음이라면 브라우저와 Node.js 시작 안내부터 확인하세요.

algorithm-interval-scheduling-greedy.mjs 파일을 만들고 아래 전체 코드를 저장합니다.

function intervalScheduling(jobs) {
  if (!Array.isArray(jobs)) throw new TypeError('일정 배열이 필요합니다.');
  const sorted = Array.from(jobs, (job,index) => {
    if (!job || typeof job.id !== 'string' || !Number.isFinite(job.start)
        || !Number.isFinite(job.end) || job.start >= job.end) {
      throw new RangeError('일정은 문자열 id와 시작 < 종료인 유한 구간이 필요합니다.');
    }
    return { job, index };
  }).sort((a,b) => a.job.end-b.job.end || a.job.start-b.job.start || a.index-b.index);
  const result = [];
  for (const { job } of sorted) {
    if (result.length === 0 || job.start >= result[result.length-1].end) result.push(job);
  }
  return result;
}

const jobs = [
  {id:'A',start:0,end:6}, {id:'B',start:1,end:4},
  {id:'C',start:3,end:6}, {id:'E',start:4,end:7},
  {id:'H',start:8,end:11}
];
console.log(JSON.stringify(intervalScheduling(jobs).map(job => job.id)));
console.log(JSON.stringify(intervalScheduling([])));

파일이 있는 폴더의 터미널에서 실행합니다.

node algorithm-interval-scheduling-greedy.mjs

기대 출력

["B","E","H"]
[]

비용과 적용 범위

일정 N개, 비교 정렬 기준 시간 O(N log N), 입력 복사와 결과 저장 공간 O(N)입니다. 종료 순 정렬을 이미 보장하면 선택 순회만 O(N)입니다. 양의 길이인 반열린 구간, 자원 하나, 동일한 일정 가치에서 선택 개수를 최대화합니다.

인터벌 스케줄링의 정렬·순회·공간 비용과 적용 조건
하나의 자원에서 가치가 같은 일정의 개수를 최대화하는 경우입니다. 원본 보기 (새 탭)

직접 확인해 보기

A=[0,5), B=[1,2), C=[2,3), D=[3,5)에서 어떤 일정을 고를까요? B와 C는 왜 함께 선택할 수 있을까요?

A[0,5), B[1,2), C[2,3), D[3,5)를 제시한 미선택 일정 질문
끝과 시작이 같은 경계에 주의해 최대 개수를 직접 골라 보세요. 원본 보기 (새 탭)
답과 이유 확인

B, C, D 세 개를 고릅니다. [start,end) 약속에서는 종료 시각이 다음 시작 시각과 같으면 겹치지 않습니다. A를 먼저 고르면 한 개만 남습니다.

참고 자료

이 글이 도움이 되었나요?

조회 중

이어서 공부하기

체크포인트: 예제의 상태 변화를 손으로 추적하고 아래 문제에 적용하세요. 값이 없는 경우와 원소 하나인 경우도 설명해 보세요. 동전 선택이 실패하는 입력과 종료 시각 기준 인터벌 선택의 교환 근거를 설명하세요.

다음 추천 · 자료구조 개념: JavaScript Union-Find: 경로 압축과 크기 합치기로 연결 상태 관리하기

다음 주제는 심화 선택 실습입니다. 연결 관리·접두사·갱신 구간·최근 사용·균형 검색 중 필요한 주제를 선택합니다.

기초·응용 경로를 마쳤습니다. 다음은 심화 선택 실습이며 필요한 주제부터 골라도 됩니다.

권장 순서는 학습 가이드와 같습니다. 다른 순서는 접힌 참고 목록에서 확인할 수 있습니다. 읽음 표시는 숙달이나 채점 결과가 아닙니다.

원래 과정 목록 순서 (선택)

읽음 기록

읽음 — / 전체 30개 · 필수 0개

이 글을 쓴 해비

웹사이트를 제작·운영하고, 개인 서비스와 자동화·AI 도구를 만듭니다. 직접 만들며 배운 내용과 문제 해결 과정을 BlogFlow에 기록합니다.

새 글 받아보기

RSS 리더에서 BlogFlow의 새 글을 확인할 수 있습니다.

RSS 피드 구독하기

댓글 남기기