Learn More →
本篇內容主要為 Grokking the Coding Interview: Patterns for Coding Questions 的翻譯與整理,行有餘力建議可以購買該專欄課程閱讀並在上面進行練習。
0056
Merge Intervals0057
Insert Interval0252
Meeting Rooms0253
Meeting Rooms II0435
Non-overlapping Intervals0729
My Calendar I0759
Employee Free Time0986
Interval List Intersections1094
Car Pooling1288
Remove Covered Intervals給定一個區間陣列,合併所有重疊的區間,並返回這些區間所組成的陣列。比如 [[1,4], [2,5], [7,9]]
合併後會得到 [[1,5], [7,9]]
:
Learn More →
Exapmles
Intervals: [[6,7], [2,4], [5,9]]
Output: [[2,4], [5,9]], Since the intervals [6,7] and [5,9] overlap, we merged them into one [5,9].
Intervals: [[1,4], [2,6], [3,5]]
Output: [[1,6]], Since all the given intervals overlap, we merged them into one.
考慮兩個區間 a
和 b
滿足 a.start <= b.start
,一共會有四種交互情況:
狀況 | 圖示 |
---|---|
|
|
我們的目標是要合併重複區間,依據上述 (2), (3), (4) 的重疊情況,合併的方式如下:
Learn More →
c.start = a.start // 左側固定
c.end = max(a.end, b.end) // 右側取大
因此我們的演算法策略為:
a.start <= b.start
b.start <= a.end
)的狀況,需要合併為新的區間 c
c
有重疊的狀況class MergeIntervals {
public:
static vector<Interval> mergeIntervals(vector<Interval> &intervals) {
if (intervals.size() < 2) {
return intervals;
}
sort(intervals.begin(), intervals.end(),
[](const Interval &x, const Interval &y) { return x.start < y.start; });
vector<Interval> mergedIntervals;
vector<Interval>::const_iterator intervalItr = intervals.begin();
Interval interval = *intervalItr++;
int start = interval.start;
int end = interval.end;
while (intervalItr != intervals.end()) {
interval = *intervalItr++;
if (interval.start <= end) {
end = max(interval.end, end);
} else {
mergedIntervals.push_back({ start, end });
start = interval.start;
end = interval.end;
}
}
mergedIntervals.push_back({ start, end });
return mergedIntervals;
}
}
class MergeIntervals {
public static List<Interval> merge(List<Interval> intervals) {
if (intervals.size() < 2) {
return intervals;
}
// sort the intervals by start index
Collections.sort(intervals, (a, b) -> Integer.compare(a.start, b.start));
List<Interval> mergedIntervals = new LinkedList<Interval>();
Iterator<Interval> intervalItr = intervals.iterator();
Interval interval = intervalItr.next();
int start = interval.start;
int end = interval.end;
while (intervalItr.hasNext()) {
interval = intervalItr.next();
if (interval.start <= end) { // overlapping intervals, adjust the end
end = Math.max(interval.end, end);
} else { // non-overlapping interval, add the previous interval and reset
mergedIntervals.add(new Interval(start, end));
start = interval.start;
end = interval.end;
}
}
// add the last interval
mergedIntervals.add(new Interval(start, end));
return mergedIntervals;
}
}
const mergeIntervals = (intervals) => {
if (intervals.length < 2) {
return intervals;
}
intervals.sort((a, b) => a.start - b.start);
const mergedIntervals = [];
let start = intervals[0].start;
let end = intervals[0].end;
for (let i = 1; i < intervals.length; ++i) {
const interval = intervals[i];
if (interval.start <= end) {
end = Math.max(interval.end, end);
} else {
mergedIntervals.push(new Interval(start, end));
start = interval.start;
end = interval.end;
}
}
mergedIntervals.push(new Interval(start, end));
return mergedIntervals;
}
def merge_intervals(intervals):
if len(intervals) < 2:
return intervals
intervals.sort(key=lambda x: x.start)
mergedIntervals = []
start = intervals[0].start
end = intervals[0].end
for i in range(1, len(intervals)):
interval = intervals[i]
if interval.start <= end:
end = max(interval.end, end)
else:
mergedIntervals.append(Interval(start, end))
start = interval.start
end = interval.end
mergedIntervals.append(Interval(start, end))
return mergedIntervals
給定一個由不重疊區間所組成的陣列,並且該陣列已經根據起始位置進行排序,將一個額外給定的區間插入正確的位置,並合併重疊區間使返回的區間陣列中為彼此獨立的區間。
Examples
Input: Intervals=[[1,3], [5,7], [8,12]], New Interval=[4,6]
Output: [[1,3], [4,7], [8,12]], After insertion, since [4,6] overlaps with [5,7], we merged them into one [4,7].
Input: Intervals=[[1,3], [5,7], [8,12]], New Interval=[4,10]
Output: [[1,3], [4,12]], After insertion, since [4,10] overlaps with [5,7] & [8,12], we merged them into [4,12].
Input: Intervals=[[2,3],[5,7]], New Interval=[1,4]
Output: [[1,4], [5,7]], After insertion, since [1,4] overlaps with [2,3], we merged them into one [1,4].
倘若給定陣列並未排序,我們可以簡單地將新的區間添加至陣列中,並呼叫 Merge Intervals 中的 merge()
函數來處理。但既然題目已經說給定的陣列 有序,我們需要想看看是否能夠有比
當我們要在一個已經排序的區間陣列中插入一個新的區間時,首先需要找到可以放置新區間的正確位置;換句話說,我們需要跳過在新區間開始前之前結束的所有區間,亦即遍歷區間陣列,並使用以下條件跳過這些不滿足條件的區間:
intervals[i].end < newInterval.start
找到正確的位置後,可以遵循類似於 Merge Intervals 的做法來插入或合併新的區間。在不失一般性的情況下將新區間稱為 a
,而滿足上述條件的第一個區間稱為 b
,則共有以下五種可能性:
狀況 | 圖示 |
---|---|
|
|
要處理上述 (2), (3), (4) 和 (5) 的重疊狀況,只需要執行以下操作:
c.start = min(a.start, b.start) // 左側取小
c.end = max(a.end, b.end) // 右側取大
因此我們的演算法策略為:
intervals[i].end < newInterval.start
的區間c
c
有重疊的狀況class InsertInterval {
public:
static vector<Interval> insert(const vector<Interval> &intervals, Interval newInterval) {
if (intervals.empty()) {
return vector<Interval> { newInterval };
}
vector<Interval> mergedIntervals;
int idx = 0;
while (idx < intervals.size() && intervals[idx].end < newInterval.start) {
mergedIntervals.push_back(intervals[idx]);
idx++;
}
while (idx < intervals.size() && intervals[idx].start <= newInterval.end) {
newInterval.start = min(intervals[idx].start, newInterval.start);
newInterval.end = max(intervals[idx].end, newInterval.end);
idx++;
}
mergedIntervals.push_back(newInterval);
while (idx < intervals.size()) {
mergedIntervals.push_back(intervals[idx]);
idx++;
}
return mergedIntervals;
}
}
class InsertInterval {
public static List<Interval> insert(List<Interval> intervals, Interval newInterval) {
if (intervals == null || intervals.isEmpty()) {
return Arrays.asList(newInterval);
}
List<Interval> mergedIntervals = new ArrayList<>();
int i = 0;
// skip (and add to output) all intervals that come before the newInterval
while (i < intervals.size() && intervals.get(i).end < newInterval.start) {
mergedIntervals.add(intervals.get(i++));
}
// merge all intervals that overlap with newInterval
while (i < intervals.size() && intervals.get(i).start <= newInterval.end) {
newInterval.start = Math.min(intervals.get(i).start, newInterval.start);
newInterval.end = Math.max(intervals.get(i).end, newInterval.end);
i++;
}
// insert the newInterval
mergedIntervals.add(newInterval);
// add all the remaining intervals to the output
while (i < intervals.size()) {
mergedIntervals.add(intervals.get(i++));
}
return mergedIntervals;
}
}
const insertInterval = (intervals, newInterval) {
let idx = 0;
let merged = [];
while (idx < intervals.length && intervals[idx].end < newInterval.start) {
merged.push(intervals[idx]);
idx += 1;
}
while (idx < intervals.length && intervals[idx].start <= newInterval.end) {
newInterval.start = Math.min(intervals[idx].start, newInterval.start);
newInterval.end = Math.max(intervals[idx].end, newInterval.end);
idx += 1;
}
merged.push(newInterval);
while (idx < intervals.length) {
merged.push(intervals[idx]);
i += 1;
}
return merged;
}
def insert_interval(intervals, new_interval):
merged = []
i = 0
start = 0
end = 1
while i < len(intervals) and intervals[i][end] < new_interval[start]:
merged.append(intervals[i])
i += 1
while i < len(intervals) and intervals[i][start] <= new_interval[end]:
new_interval[start] = min(intervals[i][start], new_interval[start])
new_interval[end] = max(intervals[i][end], new_interval[end])
i += 1
merged.append(new_interval)
while i < len(intervals):
merged.append(intervals[i])
i += 1
return merged
給定兩個區間陣列,找出兩個陣列的交集部分,每一個陣列中的區間彼此獨立且已經依據起始位置進行排序。
Examples:
Input: arr1=[[1, 3], [5, 6], [7, 9]], arr2=[[2, 3], [5, 7]]
Output: [2, 3], [5, 6], [7, 7].
Input: arr1=[[1, 3], [5, 7], [9, 12]], arr2=[[5, 10]]
Output: [5, 7], [9, 10].
這個問題可以採用 合併區間(Merge Intervals) 模式求解,正如我們在 Insert Interval 中所討論的,在兩個間隔 a
和 b
之間有五種交互的狀況;仔細觀察不難發現,每當兩個區間重疊時,其中一個區間的開始位置會位於另外一個區間中,這個事實可以幫助我們確認兩個區間是否重疊。
Learn More →
此時,兩個重疊區間交集的部分,可以透過以下方式判斷其區間:
start = max(a.start, b.start) // 左側取大
end = min(a.end, b.end) // 右側取小
因此我們只需要遍歷兩個陣列,同時查看是否有區間重疊,重疊的部分計算其交集並插入結果陣列中,直到結束。
class Intersection {
public:
static vector<Interval> merge(const vector<Interval> &arr1, const vector<Interval> &arr2) {
vector<Interval> result;
int i = 0;
int j = 0;
while (i < arr1.size() && j < arr2.size()) {
if ((arr1[i].start >= arr2[j].start) && arr1[i].start <= arr2[j].end) ||
(arr2[j].start >= arr1[i].start) && arr2[j].start <= arr1[i].end)) {
result.push_back({max(arr1[i].start, arr2[j].start), min(arr1[i].end, arr2[j].end)});
}
if (arr1[i].end < arr2[j].end) {
i++;
} else {
j++;
}
}
return result;
}
};
class IntervalsIntersection {
public static Interval[] merge(Interval[] arr1, Interval[] arr2) {
List<Interval> result = new ArrayList<Interval>();
int i = 0;
int j = 0;
while (i < arr1.length && j < arr2.length) {
if ((arr1[i].start >= arr2[j].start && arr1[i].start <= arr2[j].end)
|| (arr2[j].start >= arr1[i].start && arr2[j].start <= arr1.[i].end)) {
result.add(new Interval(Math.max(arr1[i].start, arr2[j].start), Max.min(arr1[i].end, arr2[j].end)));
}
}
if (arr1[i].end < arr2[j].end) {
i++;
} else {
j++;
}
return result.toArray(new Interval[result.size()]);
}
}
const IntersectionIntervals = (intervals_a, intervals_b) => {
let results = [];
let i = 0;
let j = 0;
while (i < intervals_a.length && j < intervals_b.length) {
if ((intervals_a[i].start >= intervals_b[j].start && intervals_a[i].start <= intervals_b[j].end) ||
(intervals_b[j].start >= intervals_a[i].start && intervals_b[j].start <= intervals_a[i].end)) {
const left = Math.max(intervals_a[i].start, intervals_b[j].start);
const right = Math.min(intervals_a[i].end, intervals_b[j].end);
result.push(newInterval(left, right));
}
if (intervals_a[i].end < intervals_b[j].end) {
i += 1;
} else {
j += 1;
}
}
return result;
}
def intersection(intervals_a, intervals_b):
result = []
i, j, start, end = 0, 0, 0, 1
while i < len(intervals_a) and j < len(intervals_b):
a_overlaps_b = intervals_a[i][start] >= intervals_b[j][start] and intervals_a[i][start] <= intervals_b[j][end]
b_overlaps_a = intervals_b[j][start] >= intervals_a[i][start] and intervals_b[j][start] <= intervals_a[i][end]
if (a_overlaps_b or b_overlaps_a):
result.append([max(intervals_a[i][start], intervals_b[j][start]), min(intervals_a[i][end], intervals_b[j][end])])
if intervals_a[i][end] < intervals_b[j][end]:
i += 1
else:
j += 1
return result
給定一組陣列,其中有
Examples
Input: [[1,4], [2,5], [7,9]]
Output: false. Since [1,4] and [2,5] overlap, a person cannot attend both of these appointments.
Input: [[6,7], [2,4], [8,12]]
Output: true. None of the appointments overlap, therefore a person can attend all of them.
Input: [[4,5], [2,3], [3,6]]
Output: false. Since [4,5] and [3,6] overlap, a person cannot attend both of these appointments.
這個問題可以採用 合併區間(Merge Intervals) 模式求解,我們需要做的是先將約會行程依據起始時間進行排序,再逐個確認這些區間是否存在重疊的狀況。
- 注意判斷的邊界需不需要包含
- 存在重疊的條件
class ConflictingAppointments {
public:
static bool canAttendAllAppointments(vector<Interval>& intervals) {
sort(intervals.begin(), intervals.end(), [](const Interval& x, const Interval& y) { return x.start < y.start; });
for (int i = 1; i < intervals.size(); i++) {
if (intervals[i].start < intervals[i - 1].end) {
return false;
}
}
return true;
}
}
class ConflictingAppointments {
public static boolean canAttendAllAppointments(Interval[] intervals) {
Arrays.sort(intervals, (a, b) -> Integer.compare(a.start, b.start));
for (int i = 1; i < intervals.length; i++) {
if (intervals[i].start < intervals[i - 1].end) {
return false;
}
}
return true;
}
}
const canAttendAllAppointments = (intervals) => {
intervals.sort((a, b) => a.start - b.start);
for (let i = 1; i < intervals.length; i++) {
if (intervals[i].start < intervals[i - 1].end) {
return false;
}
}
return true;
}
def can_attend_all_appointments(intervals):
intervals.sort(key=lambda x: x[0])
start, end = 0, 1
for i in range(1, len(intervals)):
if intervals[i][start] < intervals[i-1][end]:
return False
return True
Published on HackMD
https://hackmd.io/@Hsins授權許可
CC BY-NC-SA 4.0or
By clicking below, you agree to our terms of service.
New to HackMD? Sign up