-
Notifications
You must be signed in to change notification settings - Fork 0
253. Meeting Rooms II #21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
katsukii
wants to merge
3
commits into
main
Choose a base branch
from
253
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+249
−5
Open
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,33 +1,277 @@ | ||
## Problem | ||
// The URL of the problem | ||
|
||
https://leetcode.com/problems/meeting-rooms-ii/ | ||
|
||
## Step 1 | ||
5分程度答えを見ずに考えて、手が止まるまでやってみる。 | ||
|
||
5 分程度答えを見ずに考えて、手が止まるまでやってみる。 | ||
何も思いつかなければ、答えを見て解く。ただし、コードを書くときは答えを見ないこと。 | ||
動かないコードも記録する。 | ||
正解したら一旦OK。思考過程もメモする。 | ||
正解したら一旦 OK。思考過程もメモする。 | ||
|
||
### Approach | ||
* | ||
|
||
- 頭の中でなんとなく直感が働き starts と ends に分けて 2 つに分けてソートするところまでは自力でいったが、for 文の中身が思いつかず | ||
- 答えを見た後に時系列で図解したら理解できた | ||
- ロジックの詳細は Step2 の Approach 1 に記載 | ||
|
||
```java | ||
class Solution { | ||
public int minMeetingRooms(int[][] intervals) { | ||
if (intervals == null || intervals.length == 0) { | ||
return 0; | ||
} | ||
|
||
int[] starts = new int[intervals.length]; | ||
int[] ends = new int[intervals.length]; | ||
|
||
for (int i = 0; i < intervals.length; i++) { | ||
starts[i] = intervals[i][0]; | ||
ends[i] = intervals[i][1]; | ||
} | ||
Arrays.sort(starts); | ||
Arrays.sort(ends); | ||
|
||
int count = 0; | ||
int endIndex = 0; | ||
for (int i = 0; i < intervals.length; i++) { | ||
if (starts[i] < ends[endIndex]) { | ||
count++; | ||
continue; | ||
} | ||
endIndex++; | ||
} | ||
|
||
return count; | ||
} | ||
} | ||
``` | ||
|
||
## Step 2 | ||
|
||
他の方が描いたコードを見て、参考にしてコードを書き直してみる。 | ||
参考にしたコードのリンクは貼っておく。 | ||
読みやすいことを意識する。 | ||
他の解法も考えみる。 | ||
|
||
- ## https://github.com/olsen-blue/Arai60/pull/57/files | ||
|
||
### Approach 1. 開始・終了時刻それぞれのソート済配列を使用 | ||
|
||
時間計算量: O(n log n) ※ 配列のソート | ||
空間計算量: O(n) | ||
|
||
- すべての会議の開始時刻を配列 starts に格納し、昇順でソート。終了時刻も同様に配列 ends に格納し、昇順でソート | ||
- 開始時刻配列をループで走査。新しい会議が始まる前に終わる会議があれば、その会議室を再利用。そうでなければ、新しい会議室が必要 | ||
- 感想 | ||
- Step1 ですんなりいけるかと思ったが意外と頭がこんがらがっててこずった | ||
- 開始時刻配列と終了時刻配列それぞれをソートした時点で、それぞれのインデックスが必ずしも同一の会議を指していない事を直感で理解できていなかったことが原因 | ||
- 時系列に図解してそれぞれの時刻にインデックスをナンバリングしたらようやく理解できた | ||
|
||
```java | ||
class Solution { | ||
public int minMeetingRooms(int[][] intervals) { | ||
if (intervals == null || intervals.length == 0) { | ||
return 0; | ||
} | ||
|
||
// Separate intervals array into starts and ends | ||
int[] starts = new int[intervals.length]; | ||
int[] ends = new int[intervals.length]; | ||
|
||
for (int i = 0; i < intervals.length; i++) { | ||
starts[i] = intervals[i][0]; | ||
ends[i] = intervals[i][1]; | ||
} | ||
Arrays.sort(starts); | ||
Arrays.sort(ends); | ||
|
||
int roomCount = 0; | ||
int endIndex = 0; | ||
for (int i = 0; i < intervals.length; i++) { | ||
if (starts[i] < ends[endIndex]) { | ||
roomCount++; | ||
continue; | ||
} | ||
endIndex++; | ||
} | ||
|
||
return roomCount; | ||
} | ||
} | ||
``` | ||
|
||
### Approach 2. 最小ヒープ(PriorityQueue)を用いる方法 | ||
|
||
時間計算量: O(n log n) | ||
空間計算量: O(n) | ||
|
||
- 会議を開始時刻でソート | ||
- 最小ヒープを使って**現在進行中の**会議の終了時刻を追跡 | ||
- 新しい会議が始まる時、ヒープから終了済会議を取り除く | ||
- ヒープのサイズが必要な会議室の数になる | ||
- 参考 | ||
- https://github.com/Ryotaro25/leetcode_first60/pull/61/files#diff-92e9dbf517861f420e88aa4cedcec79ceabc8fc133bc619d972cf04f4d3fc280R1 | ||
- わかりやすい。Heap はいろんなところで見かけるので慣れておきたい | ||
|
||
```java | ||
public class Solution { | ||
public int minMeetingRooms(int[][] intervals) { | ||
if (intervals == null || intervals.length == 0) { | ||
return 0; | ||
} | ||
|
||
// Sort by the start time of each meeting | ||
Arrays.sort(intervals, (a, b) -> Integer.compare(a[0], b[0])); | ||
|
||
// Trace the end-time as minimum rooms | ||
PriorityQueue<Integer> minEndTimeHeap = new PriorityQueue<>(); | ||
minEndTimeHeap.add(intervals[0][1]); // Add first mtg end-time | ||
|
||
// Second and later meetings | ||
for (int i = 1; i < intervals.length; i++) { | ||
// If current mtg starts after earliest ending meeting | ||
if (intervals[i][0] >= minEndTimeHeap.peek()) { | ||
minEndTimeHeap.poll(); // Release the meeting room to reuse | ||
} | ||
// Assign the current end-time as a newly necessary meeting room | ||
minEndTimeHeap.add(intervals[i][1]); | ||
} | ||
|
||
return minEndTimeHeap.size(); | ||
} | ||
} | ||
``` | ||
|
||
### Approach 3. スイープライン+累積和(イベントソート版) | ||
|
||
時間計算量: O(n log n) | ||
空間計算量: O(n) | ||
|
||
- https://github.com/olsen-blue/Arai60/pull/57/files#diff-a0ae933995d3a32d66b233c1e96d7f1bbe7ff33e80eb0997d04a4806ba5d2be5R112-R135 | ||
|
||
- こちらを参考に作成した | ||
- 考え方は分かりやすくて好みだが、Java だと行数が増えるため実践では選びづらい | ||
|
||
- スイープラインとは | ||
|
||
- 特定の「線」(または点)を、ある空間(たとえば平面や時間軸)上で一定方向に動かしながら、途中で発生する「イベント」を順次処理していくアルゴリズム設計の手法をこう呼ぶらしい | ||
- この問題で初めて知った | ||
- https://en.wikipedia.org/wiki/Sweep_line_algorithm#:~:text=In%20computational%20geometry%2C%20a%20sweep,critical%20techniques%20in%20computational%20geometry. | ||
|
||
- イベント変換: 各会議の開始時刻と終了時刻をそれぞれイベントとして配列に変換する | ||
- 開始イベント: +1 カウント(会議室使用開始) | ||
- 終了イベント: -1 カウント(会議室リリース) | ||
- イベントソート: 両イベント配列を 2D 配列として一つの配列にマージして時刻順(昇順)にソートする。同時刻にイベントが複数存在する場合、終了イベント(-1)を優先する | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 時刻ごとにいくつ増えるか減るか、と思うこともできますね。 全体的に問題ないかと思います。 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ありがとうございます。 |
||
- スイープ処理: ソート済のイベントをループで順番に処理し、イベントごとにカウントを更新する。カウントの最大数が必要な会議室の最小数となる | ||
|
||
```java | ||
public class Solution { | ||
public int minMeetingRooms(int[][] intervals) { | ||
if (intervals == null || intervals.length == 0) { | ||
return 0; | ||
} | ||
|
||
// Store into an array for each start and end event | ||
int[][] events = new int[intervals.length * 2][2]; | ||
int index = 0; | ||
for (int[] interval : intervals) { | ||
// Events [Start: +1], [End: -1] | ||
events[index++] = new int[]{interval[0], 1}; // Start | ||
events[index++] = new int[]{interval[1], -1}; // End | ||
} | ||
|
||
// Sort by time (if the times are the same, the end time takes priority) | ||
Arrays.sort(events, (a, b) -> { | ||
if (a[0] != b[0]) { | ||
return Integer.compare(a[0], b[0]); // by time | ||
} | ||
return Integer.compare(a[1], b[1]); | ||
}); | ||
|
||
int roomsInUse = 0; | ||
int maxRooms = 0; | ||
// Process events sequentially | ||
for (int[] event : events) { | ||
roomsInUse += event[1]; // Start:+1, End:-1 | ||
maxRooms = Math.max(maxRooms, roomsInUse); | ||
} | ||
|
||
return maxRooms; | ||
} | ||
} | ||
``` | ||
|
||
### Approach 4. スイープライン+累積和(座標圧縮 + 差分配列版) | ||
|
||
- Approach 3 の亜種 | ||
- 252. Meeting Rooms の Approach 4 とほぼ同一の手法。返り値が違うだけ | ||
- https://github.com/katsukii/leetcode/pull/20 | ||
|
||
```java | ||
public class Solution { | ||
public int minMeetingRooms(int[][] intervals) { | ||
// 1. Collect all times | ||
List<Integer> times = new ArrayList<>(); | ||
for (int[] interval : intervals) { | ||
times.add(interval[0]); // Start | ||
times.add(interval[1]); // End | ||
} | ||
|
||
// 2. Remove duplicates and sort | ||
Set<Integer> uniqueTimes = new TreeSet<>(times); | ||
List<Integer> sortedTimes = new ArrayList<>(uniqueTimes); | ||
|
||
// 3. Cordinate compression | ||
Map<Integer,Integer> compressedTimes = new HashMap<>(); | ||
for (int i = 0; i < sortedTimes.size(); i++) { | ||
compressedTimes.put(sortedTimes.get(i), i); | ||
} | ||
|
||
// 4. Difference array | ||
int[] diff = new int[sortedTimes.size() + 1]; | ||
|
||
// 5. Set +1 / -1 | ||
for (int[] interval : intervals) { | ||
int start = compressedTimes.get(interval[0]); | ||
int end = compressedTimes.get(interval[1]); | ||
diff[start] += 1; | ||
diff[end] -= 1; | ||
} | ||
|
||
// 6. Check prefix sum, which means ongoing meetings. | ||
int roomsInUse = 0; | ||
int maxRooms = 0; | ||
for (int i = 0; i < sortedTimes.size(); i++) { | ||
roomsInUse += diff[i]; | ||
maxRooms = Math.max(maxRooms, roomsInUse); | ||
} | ||
return maxRooms; | ||
} | ||
} | ||
``` | ||
|
||
## Step 3 | ||
|
||
今度は、時間を測りながら、もう一回書く。 | ||
アクセプトされたら消すを3回連続できたら問題はOK。 | ||
アクセプトされたら消すを 3 回連続できたら問題は OK。 | ||
|
||
- Approach 2 の minHeap で解いた | ||
|
||
```java | ||
public class Solution { | ||
public int minMeetingRooms(int[][] intervals) { | ||
Arrays.sort(intervals, (a, b) -> Integer.compare(a[0], b[0])); | ||
|
||
PriorityQueue<Integer> minEndTimeHeap = new PriorityQueue<>(); | ||
minEndTimeHeap.add(intervals[0][1]); // first mtg | ||
for (int i = 1; i < intervals.length; i++) { | ||
if (minEndTimeHeap.peek() <= intervals[i][0]) { | ||
minEndTimeHeap.poll(); | ||
} | ||
minEndTimeHeap.add(intervals[i][1]); | ||
} | ||
return minEndTimeHeap.size(); | ||
} | ||
} | ||
``` |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
intervals == null はともかく、空ならば特に問題なく0が返りますか?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
はい、空配列の場合
intervals.length == 0
がtrueとなります。