Merge Intervals
2022/7/30
來補之前面試白板題沒解的題目了,本來想學學唐豪寫一篇專門的 leetcode 筆記,但總覺得只寫刷題的內容真的是挺無聊的,而且我也不是每題都有耍笨的題材可以寫,所以就不特別開新的分類,有比較好玩或者有故事的再來寫一篇分享。
Merge Intervals
這題其實最早我不是在 leetcode 上看到的,是我剛開始上班時為了練習 js,無意間找到的一個練習題,本質與這題相同就只是描述不一樣而已,過幾個月跟著唐神刷題的時候,看到他寫了這題我就跟著寫,面試的時候也剛好被問到差不多的題目,運氣真好,感恩唐神讚嘆唐神。
題目描述
面試的時候面試官是說給你一串時間紀錄,有開始與結束的時間,如果發現有重疊的時間,就要將它合併,輸出就是沒有重疊的時間區間紀錄。
leetcode 範例:
Input: intervals = [[1,3],[2,6],[8,10],[15,18]]
Output: [[1,6],[8,10],[15,18]]
[1, 3]
跟[2, 6]
有重複的區間所以要合併成[1, 6]
想法
這題我分享一下第一次寫這題時的想法好了,我一開始想著的是把這些時間排序,先標記每個時間是開始(start)還是結束(end),然後直接排序成1、2、3、6、8、10、15、18
,從左到右掃一遍,第一個遇到的開始時間一定就是區間的開始,遇到連續的開始就代表有重疊的時間,所以有幾個開始就要有幾個結束,後面不管遇到是開始還是結束時間,只要遇到的開始與結束時間數量一樣,那就結束這個區段。
只要一個區間結束就把開始與結束時間的數量重置為 0,繼續找尋下一個區間。
實作
這邊直接拿之前用 js 寫的 code 稍做修改,還原我當時有多蠢
function mergeIntervals(intervals) {
if (intervals.length === 0) return [];
const newIntervals = [];
// 標註開始與結束 這邊完全是多此一舉XDDD
for (const interval of intervals) {
newIntervals.push({ value: interval[0], state: 'start' });
newIntervals.push({ value: interval[1], state: 'end' });
}
// 根據時間排序
newIntervals.sort((a, b) => a.value - b.value);
const mergedIntervals = [];
let startCount = 0; // 紀錄遇到幾個開始時間
let endCount = 0; // 紀錄遇到幾個結束時間
for (let i = 0; i < newIntervals.length; i++) {
if (newIntervals[i].state === 'start') {
startCount++;
if (startCount === 1) {
// 第一個開始時間一定是一個區間的開始
mergedIntervals.push([newIntervals[i].value]);
}
} else if (newIntervals[i].state === 'end') {
endCount++;
if (startCount === endCount) {
// 當開始時間與結束時間數量相等時,代表這個區間已經結束
mergedIntervals[mergedIntervals.length - 1].push(newIntervals[i].value);
startCount = 0; // 重置
endCount = 0;
}
}
}
return mergedIntervals;
}
其實這樣寫會有個小問題,例如:[[1, 4],[4, 7]]
這種相連的就會有問題,但是他也是屬於要合併的區間!
以兩個區間來討論的話,只會有四種情況。
- 兩個區間完全分開
- 兩個區間完全重疊,其中一個包含在另一個裡面
- 兩個區間有部分重疊
- 兩個區間是接續的沒有重疊
我就是忽略了第 4 種情況,所以要加上下面這段,判斷是不是相連。
if (i !== newIntervals.length - 1) {
if (newIntervals[i].value === newIntervals[i + 1].value) {
continue; // 如果跟下一項是一樣的就跳過
}
}
優化
這邊我就參考唐神筆記-56. Merge Intervals寫的用 Java 改寫了,唐神這題甚至連 Thinking Process 都空白,顯然就是覺得這題跟呼吸一樣自然,沒有寫的必要。
也是一樣做排序,只是不用多此一舉去標開始與結束,因為其實第一項就是開始、第二項就是結束啊,我特別標記他要幹嘛,array[i][0]
就是開始時間array[i][1]
就是結束時間。
而且就只要往後比較,如果有發生重疊或連續的情況(上圖 2、3 跟 4),就直接取結束時間最晚的那個當結束時間就好了,這樣就一次搞定三種情況。
public int[][] merge(int[][] intervals) {
if (intervals.length == 0) return new int[][]{};
Arrays.sort(intervals, Comparator.comparingInt(a -> a[0]));
ArrayList<int[]> result = new ArrayList<>();
int[] current = intervals[0];
for (int i = 1; i < intervals.length; i++) {
// 如果當前區間的開始時間小於等於前面的結束時間,代表有重疊或連續的情況
if (intervals[i][0] <= current[1]) {
// 取結束時間的最大值為新的結束時間
current[1] = Math.max(current[1], intervals[i][1]);
} else {
// 沒重疊也沒連續就直接加入list中
result.add(current);
current = intervals[i];
}
}
result.add(current);
return result.toArray(new int[result.size()][]);
}
兩個解法的時間複雜度都是
但是唐神的解法簡單易懂,不像我寫了一大堆的 if,然後還有沒判斷到的情況,還要再加更多的 if 進去,這就是我與唐神的差距啊!
很多好解法的 code 都一個比一個精簡,但我每次做一道新的題目都是像上面那樣寫得又臭又長,然後還可能是錯的 XDD
function merge(intervals) {
if (intervals.length === 0) return [];
const newIntervals = [];
intervals.sort((a, b) => a[0] - b[0]);
let current = intervals[0];
for (const interval of intervals) {
if (interval[0] <= current[1]) {
current[1] = Math.max(current[1], interval[1]);
} else {
newIntervals.push(current);
current = interval;
}
}
newIntervals.push(current);
return newIntervals;
}
補上 js 版本,跟上面可以做個對照
2022/9/6
986. Interval List Intersections
這題其實想法一樣,只是變成取交集而已,我就不寫了~
Reference
心得
記得我第一次寫 leetcode 的時候,幾乎都要想非常久,思考過程也是蠢得不行,就算想到了,要寫出來也是要花點時間,真的是最爛的那種。
本來以為刷題是為了訓練思考邏輯能力,後來發現我練習到比較多的應該是要注意邊界,要先把所有可能情況列出來再開始思考,我真的很常忽略了一些例外情況,之前工作上寫圍棋邏輯的東西也是會忘記一些邊界情況,其實現在也還是ㄏㄏ。
應該有不少人覺得刷題很無聊或是毫無章法很像在背答案的,這邊分享一下我自己的做法,我都會把一些題目轉化成生活中的例子,或是找一些有趣的應用來連結,還會用我一些生活經驗來做記憶,比如之前分享過的洗牌演算法跟 LIS,這樣子寫過都很難忘掉,我自己覺得這樣刷起題來還蠻快樂的。
新登場人物
唐豪:
唐神,研究所同學,是個只要給他個死線就能把大象塞進冰箱的超人,是個低調的強者,打開他的筆電會發現 OS 是 kali linux,會特地給難吃的店五顆星,並寫上這麼優質的店家不能只有我吃到的評論,裸考多益 900 分跟我說畢業門檻很好過,結果我考 500。
順便分享唐神的 leetcode 筆記我都是看這個在學習的。
琛哥:
琛神,研究所同學,是個超級認真負責的人,幾乎每天都在實驗室從早待到晚,是我們這屆最認真的人沒有之一,跟志宏學長一起處理實驗室的大小事,根本是志宏學長接班人,連老師都問他要不要奮力一「博」,老師還是在口試的時候說的,嚇得琛哥人都沒了。
當時與琛哥、唐豪有個小群組,琛神唐神與他們的快樂夥伴,沒錯我就是那個快樂夥伴,負責搞笑的,這群組一開始是一起趕論文,後來是一起刷題一起找工作,現在變成我膜拜他們用的群組,感恩琛神唐神讚嘆琛神唐神!
萱姐一直說我們創小群組排擠他,但當時他的論文是最早完成的,所以不用跟我們一起趕,當我們畢業即失業的時候,萱姐早已有工作了,直接進入 intel 跟鬼一樣,根本不用跟我們一起刷題找工作啊。
萱姐:
實驗室之花,研究所同學,馬祖人,據說在馬祖報他的名字就可以呼風喚雨,超愛吃分部門口的神秘花園,老闆娘根本就把萱姐當乾女兒,所以我們去吃都會被老闆娘招待,上次回去老闆娘還把我跟唐豪當成萱姐的學弟ㄏㄏ,好久沒被當成學生了好開心,感恩萱姐讚嘆萱姐。
印象最深的是有一次不知道他是染頭髮還是剪頭髮,結果一整天我、琛哥還有吉神都沒有發現,直到乃元學長進來說:欸?你剪頭髮喔?(或是染或燙吧反正我真的沒看出來),萱姐才很開心終於有人發現,然後用高 8 度的音調說我們都瞎了。
宣傳一下如果要去馬祖玩可以考慮住日光春和,這是萱姐家開的民宿,冠毅大佛去了都說讚。