현재 알고리즘 스터디에서 매주 LeetCode의 Weekly Contest 문제를 풀고 있습니다. 이번 주에는 Weekly Contest 400에 출제된 "Count Days Without Meetings" 문제를 풀면서 Line Sweep Algorithm을 알게 되었습니다. 문제를 해결하기 위해 Discuss 탭에서 힌트를 얻었고, 그 과정에서 이 알고리즘을 접하게 되었습니다.
Line Sweep Algorithm은 "선 스위핑 알고리즘"으로 번역되며, 주로 좌표 평면 상의 여러 선이나 구간이 겹치는 문제를 해결하는 데 사용됩니다. 이 알고리즘은 하나의 수직선(선 스위프)를 가로질러 이벤트들을 처리하면서 문제를 해결하는 방식으로 동작합니다.
Count Days Without Meetings 문제
직원이 근무할 수 있는 총 일수(1일부터 시작)를 나타내는 양의 정수 일수가 제공됩니다. 또한 크기 n의 2D 배열 회의가 제공됩니다. 여기서 회의[i] = [start_i, end_i]는 회의 i(포함)의 시작 및 종료 날짜를 나타냅니다.
직원이 근무할 수 있지만 예정된 회의가 없는 일수를 반환합니다.
참고: 회의가 겹칠 수 있습니다.
Input: days = 5, meetings = [[2, 4], [1, 3]]
Output: 1
Explanation: There is no meeting scheduled on the 5th day.
문제는 주어진 총 일수와 각 회의의 시작 및 종료 날짜가 주어졌을 때, 회의가 없는 날의 수를 계산하는 문제입니다. 문제 설명에서 회의가 겹칠 수 있습니다. 라는 문장을 통해 구간이 겹치는 부분이 있다는 것을 알 수 있습니다. Line Sweep Algorithm은 구간이 겹치는 문제를 해결하는 데 효과적이므로, 이 문제에 적합하다고 생각합니다.
Line Sweep Algorithm은 시간 또는 공간을 따라가면서 이벤트를 처리하는 방식입니다. 이 문제에서는 날짜를 따라가면서 각 날짜에 회의가 있는지 확인하는 방식으로 문제를 해결합니다.
availableDays: 회의가 없는 날의 수를 저장할 변수currMeetings: 현재 진행 중인 회의 수를 추적할 변수prevDay: 마지막으로 처리한 날짜를 추적할 변수var countDays = function (days, meetings) {
const START = "start";
const END = "end";
// 이벤트를 저장할 배열 생성
const events = [];
// meetings 배열의 각 요소를 순회하며 이벤트 생성
for (const [s, e] of meetings) {
events.push([s, START]); // 시작일 이벤트 추가
events.push([e + 1, END]); // e + 1의 의미? 끝난 다음 날을 나타냄. 종료일 이벤트 추가
}
// events 배열 정렬
// [day, event type]
events.sort((a, b) => {
// 날짜가 다른 경우, 날짜 기준으로 정렬
if (a[0] !== b[0]) {
return a[0] - b[0];
}
// 날짜가 같은 경우, 이벤트 타입이 "start"가 "end"보다 앞에 오도록 정렬
else {
// a는 start type, b는 end type 일 경우 a가 먼저 오도록 음수 반환
if (a[1] === START && b[1] === END) {
return -1;
}
// a가 end type, b가 start type일 경우 b가 먼저 오도록 양수 반환
else if (a[1] === END && b[1] === START) {
return 1;
}
// 둘 다 같은 타입인 경우, 순서 변경 없음
else {
return 0;
}
}
});
// 회의가 없는 날을 저장할 변수 availableDays 선언 및 초기화
let availableDays = 0;
// 현재 진행 중인 meeting 수를 저장할 변수 currMeetings 선언 및 초기화
let currMeetings = 0;
// 현재 이벤트를 처리하기 전에 마지막으로 처리한 날짜를 저장하는 변수 prevDay 선언 및 초기화
let prevDay = 1;
// events 배열 순회
for (const [day, type] of events) {
// 이벤트를 마지막으로 처리한 날과 현재 이벤트의 날 사이에 회의가 없는 날을 계산
if (prevDay < day) {
// 현재 진행되는 회의가 없는 경우 availableDays에 비어있는 날 추가
if (currMeetings === 0) availableDays += day - prevDay;
// prevDay를 현재 이벤트의 날로 업데이트
prevDay = day;
}
// type이 start 인 경우 현재 진행중인 회의 증가
if (type === START) currMeetings++;
// type이 end 인 경우 현재 진행중인 회의 감소
else currMeetings--;
}
// 모든 이벤트가 처리된 후, 마지막 이벤트 날짜 이후부터 주어진 총 일수까지 회의가 없는 날을 계산
if (prevDay <= days && currMeetings === 0)
availableDays += days - prevDay + 1;
// 회의가 없는 비어있는 날 반환
return availableDays;
};
총 시간복잡도: O(nlogn)