# 2025 年「資訊科技產業專案設計」作業 1
> 貢獻者: 摩斯 Morse
> [video]()
🧑💻 : interviewer 👨🎓 : interviewee
> [模擬面試錄影: 漢1](https://youtu.be/bIImb870RAE)
> [模擬面試錄影: 漢2](https://youtu.be/__W9F6W3Cos)
> [模擬面試錄影: 英](https://youtu.be/egmHJLuDYLU)
## [21. Merge Two Sorted Lists](https://leetcode.com/problems/merge-two-sorted-lists/description/)
#### 測驗說明與問答
>You are given the heads of two sorted linked lists list1 and list2.
>
>Merge the two lists into one sorted list. The list should be made by splicing together the nodes of the first two lists.
>
>Return the head of the merged linked list.
#### 面試紀錄
🧑💻 : 在第一道題目中,給你兩個排序過的 linked list ,請你將這兩者合併後,回傳合併後的 linked list 的 head。
👨🎓 : 所以我的程式碼的輸入會是兩個 list 的 head , 我需要將這兩個 list 做合併後回傳合併後 list 的 head 這樣正確嗎 ?
🧑💻 : 沒錯,你的理解是對的。
👨🎓 : 想請問合併後的 linked list 需要排序完成嗎 ?
🧑💻 : 是的,合併後的陣列應該要是排序過的。
👨🎓 : 有可能會有空的 list 的情況嗎 ?
🧑💻 : 有可能,需要考慮進去。
👨🎓 : 了解。為了確認我有理解,我想舉個例子。假設題目給兩個排序過的 list 分別是 (1 $\to$ 4 $\to$ 5 $\to$ 6) 及 (1 $\to$ 3 $\to$ 4 $\to$ 7) 最後結果應該要是 (1 $\to$ 1 $\to$ 3 $\to$ 4 $\to$ 5 $\to$ 6 $\to$ 7), 這樣正確嗎 ?
🧑💻 : 沒錯。
👨🎓 : 我目前直覺的想法是,先建立一個 dummyHead 並且用一個變數 curr 來追蹤目前處理到哪個 node。在兩個 list 都還有元素的情況下,先判斷哪個目前的 head 值最小,依序接在 head 這個新的 list 後方,當有一個 list 已經空掉後便把另一個直接接在 curr 後方,最後再回傳 dummyHead 的 next 。
🧑💻 : 這樣時間複雜度為何呢 ?
👨🎓 : 假設兩個 list 分別長度是 m 跟 n ,最差的情況下我會需要迴圈到每個元素,因此會是 O(m+n)。
#### Iterative version
```cpp
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
ListNode dummyHead = ListNode();
ListNode* curr = &dummyHead;
while(list1 && list2){
if(list1->val < list2->val){
curr->next = list1;
list1 = list1->next;
}else{
curr->next = list2;
list2 = list2->next;
}
curr = curr->next;
}
curr->next = (list1)?list1:list2;
return dummyHead.next;
}
```
<!-- Optimize:
```cpp
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
ListNode *head;
ListNode** tail = &head;
while(list1 && list2){
if(list1->val < list2->val){
*tail = list1;
list1 = list1->next;
}else{
*tail = list2;
list2 = list2->next;
}
tail = &((*tail)->next);
}
*tail = (list1)?list1:list2;
return head;
}
``` -->
🧑💻 : 是否有不是 iterative 的方法呢?
👨🎓 : 我的想法是可以把問題切成小塊,每次先比較兩個 list 目前 node 的數值後,遞迴處理剩下的內容。如果目前的 list 是空的,則直接回傳另一個 list 接在後面。
#### Recursive version
```cpp
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
if(!list1) return list2;
if(!list2) return list1;
if(list1->val < list2->val){
list1->next = mergeTwoLists(list1->next,list2);
return list1;
}else{
list2->next = mergeTwoLists(list1,list2->next);
return list2;
}
}
```
## [104. Maximum Depth of Binary Tree](https://leetcode.com/problems/maximum-depth-of-binary-tree/description/)
#### 測驗說明與問答
> Given the root of a binary tree, return its maximum depth.
>
>A binary tree's maximum depth is the number of nodes along the longest path from the root node down to the farthest leaf node.
#### 面試紀錄
🧑💻 : 在第二道題目中,你會有一個 Binary Tree 當作 input,你需要回傳這棵 Binary Tree 的 Maximum Depth。
👨🎓 : 好,所以我的程式碼會需要一個 input ,可能會是一個 Binary Tree 的 root,我需要去計算整棵 Binary Tree 的最大深度之後回傳一個整數值,表示這顆樹的深度。
👨🎓 : 我這邊想到的是用 DFS 的方式去處理,因為 root 的深度會比左右兩個子節點深度中最大的那個再加一,因此我的 DFS 回傳數值會是兩者做 DFS 後的最大值再加上 1。這樣就可以一路從葉節點往上推得 Binary Tree 的深度了。
🧑💻 : 這樣時間複雜度會是多少呢?
👨🎓 : 因為每個節點都會被探索到一次,所以時間複雜度會是 O(N),其中 N 是節點數量。
🧑💻 : 好,你可以開始打程式碼了。
#### dfs
```cpp
int dfs(TreeNode* root) {
if(!root){
return 0;
}
return max(dfs(root->right), dfs(root->left)) + 1;
}
int maxDepth(TreeNode* root) {
return dfs(root);
}
```
<!-- #### bfs
```cpp
int maxDepth(TreeNode* root) {
if (!root) return 0;
std::queue<TreeNode*> q;
q.push(root);
int depth = 0;
while(!q.empty()){
int size = q.size();
for(int i = 0; i< size; i++){
TreeNode* node = q.front();
q.pop();
if (node->left) q.push(node->left);
if (node->right) q.push(node->right);
}
depth ++;
}
return depth;
}
``` -->
## [621. Task Scheduler](https://leetcode.com/problems/task-scheduler/description/)
#### 測驗說明與問答
>You are given an array of CPU tasks, each labeled with a letter from A to Z, and a number n. Each CPU interval can be idle or allow the completion of one task. Tasks can be completed in any order, but there's a constraint: there has to be a gap of at least n intervals between two tasks with the same label.
>
>Return the minimum number of CPU intervals required to complete all tasks.
#### 面試紀錄
🧑💻 : Since you have some experience developing schedulers with sched_ext, let’s have a small exam about scheduling.
👨🎓 : Sure!
🧑💻 : Here’s the problem. You are given an array of tasks, each labeled with a letter from A to Z. In each CPU interval, the CPU can either stay idle or execute one task. There is one constraint: there must be at least n intervals between two tasks with the same label.
👨🎓 : Is there only a single CPU?
🧑💻 : Yes, assume there is just one CPU available.
👨🎓 : Okay! My first approach would be to build a max-heap that stores the remaining counts of tasks that are ready to be scheduled. In addition, I would maintain a waiting queue to keep track of tasks that are cooling down. For each entry in the waiting queue, I would store both the remaining count of that task and the earliest time when it can be scheduled again.
🧑💻 : And how would you represent the tasks in the max-heap?
👨🎓 : Instead of storing the task labels themselves, I would store their remaining counts, since the actual scheduling only depends on how many times each task still needs to be executed.
```cpp
int leastInterval(vector<char>& tasks, int n) {
vector<int> Count(26, 0);
for (int i = 0; i < tasks.size(); i++) {
Count[tasks[i] - 'A']++;
}
priority_queue<int> maxHeap;
for (int i = 0; i < 26; i++) {
if (Count[i] != 0) {
maxHeap.push(Count[i]);
}
}
queue<pair<int, int>> waiting_queue;
int time = 0;
while (!maxHeap.empty() || !waiting_queue.empty()) {
time++;
while (!waiting_queue.empty() &&
waiting_queue.front().second <= time) {
maxHeap.push(waiting_queue.front().first);
waiting_queue.pop();
}
if (!maxHeap.empty()) {
int top = maxHeap.top() - 1;
maxHeap.pop();
if (top != 0) {
waiting_queue.push({top, time + n + 1});
}
}
}
return time;
}
```
🧑💻 : Great. Then what’s the time complexity of your algorithm?
👨🎓 : To build the frequency count of the tasks, we only need to traverse all T tasks, so that takes O(T). Building the max-heap involves at most 26 task types, which is effectively O(1). In the main loop, each push and pop operation on the max-heap costs O(log k), where k is the heap size. Since each task can only be pushed and popped once, this part totals to O(T log k). But since k ≤ 26, we can treat log k as a constant. So overall, the time complexity is O(T).
🧑💻 : Well done. That concludes today’s interview. Thanks for your time, and I hope to see you again!
## 初步檢討
#### 21. Merge Two Sorted Lists
##### Interviewer
- 回應較少, 只是單純回答是非題而已
- 沒有回答到 interviewee 詢問舉例是否正確
- 對於完成的程式碼沒有過多的詢問,尤其後半段 recursive 部份
##### Interviewee
- 應關閉編譯器提示功能或是改用記事本, 避免彈出視窗干擾。Ex: 打範例時輸錯成 int_64
- 精確的說回傳的是"指向 1 這個的指標" 而不是說 "回傳 1 這個的位置",不夠精確
- 在 Approach 部份說明較亂,並沒有明確的說到解題的關鍵,只是單純把後面要打的程式碼一步一步講出來, 但沒看程式碼的人可能會聽不懂自己在講啥。
- Coding 部份,可以適時的打上一些註解。沒有解釋為何需要建立 dummyHead 或是解釋目前這樣寫的目的為何。
- 雖然問了 "有可能會有空的 list 的情況嗎 ?" 但並沒有特別處理。
- 在說明 recursive 方式部份講解不清楚
#### 104. Maximum Depth of Binary Tree
##### Interviewer
- 沒有回應到 Interviewee 重述題目的部份
##### Interviewee
- 同樣的應該要關閉編譯器提示功能
- 解釋 revursive 時不太清楚,沒有確實的講到為何需要用 recursive ,這樣聽起來很像本來就知道答案了。
- 舉例時花太多時間畫樹,且忘記打成註解
#### 621. Task Scheduler
##### Interviewer
- Interviewee 講完 Approach 後沒有再追問相關的內容
##### Interviewee
- 太多常用的語助詞: so, that... 之類的
- 沒有先確認就直接講 Approach
- 打鍵盤聲音偏大, 編譯器的顏色可能導致看不清楚
- 應該先解釋完要幹麻再開始打字
- 全部打完的時候可以簡述程式碼會如何運作