# 2023 年「[資訊科技產業專案設計](https://hackmd.io/@sysprog/info2023/https%3A%2F%2Fhackmd.io%2F%40sysprog%2FS11ooE4Rh)」作業 1
> 貢獻者: 林克-Link
> [video (英)](https://www.youtube.com/watch?v=sEyuWghSS2M)
> [video (漢)](https://www.youtube.com/watch?v=PoYD9bZQCsM)
> [video (漢)](https://www.youtube.com/watch?v=nd9EUHtTjdM)
🧔:interviewer 👶:interviewee
## [Easy 21. Merge Two Sorted List](https://leetcode.com/problems/merge-two-sorted-lists/)
### 測驗說明與問答
🧔: You're given two sorted linked lists, and you need to merge them into one list with the sorted order.
👶: Ok, Let me confirm the question. I need to make two sorted list into one list with ascending or decending order. Depend on the order of the given list. Is that correct?
🧔: Yes, and you can assume they are in ascending order.
👶: OK. So, if I have two lists with 1,2,3 and 2,3,4 respectively the answer should be 1,2,2,3,3,4
🧔: Exactly, you can start you code.
👶: In my intuition, I will approch it with iterate through every node in two list by comparing each other everytime.
```c
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2){
struct ListNode *tmp = NULL, *ans = NULL;
while (list1 && list2){
if (list1->val > list2->val){
if (tmp == NULL){
ans = tmp = list2;
}else{
tmp->next = list2;
tmp = tmp->next;
}
list2 = list2->next;
}else{
if (tmp == NULL){
ans = tmp = list1;
}else{
tmp->next = list1;
tmp = tmp->next;
}
list1 = list1->next;
}
}
if (tmp){
if (list2) tmp->next = list2;
if (list1) tmp->next = list1;
}else{
ans = list1 ? list1 : list2;
}
return ans;
}
```
👶: So, Since we iterate though each node in worst case, the time complexity should be $O(m+n)$.
🧔: It looking great. But the control statement may cause control hazard. Can you reduce the if statement?
👶: I will start from reducing the special case. So the specail case is when ...
```c
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2){
if (list1 == NULL) return list2;
if (list2 == NULL) return list1;
struct ListNode *ans = NULL, **tmp = &ans ;
while (list1 && list2){
if (list1->val > list2->val){
*tmp = list2;
tmp = &(*tmp)->next;
list2 = list2->next;
}else{
*tmp = list1;
tmp = &(*tmp)->next;
list1 = list1->next;
}
}
if (list2) *tmp = list2;
else *tmp = list1;
return ans;
}
```
🧔: OK, I think it great enough. Since we don't have too much time left. Let's end up here. Thank you for your time.
### 初步檢討
**Interviewer**
* [0:06](https://youtu.be/TBOPe1dJUhA?si=3tpTKjLlKHA7vbvj&t=6): "The question today will be in it" 可以改成 "The question we will discuss later will be in it" 避免讓面試聽起來像是在考試。
* 沒有發現 interviewee 在 [14:16](https://youtu.be/TBOPe1dJUhA?si=mpMpQjTfTbFLPW4k&t=856) 程式碼 trace 時發生錯誤。
**Interviewee**
* 最終程式碼還可以再更精簡
* [1:18](https://youtu.be/TBOPe1dJUhA?si=4TBYydeiTIOsPJXK&t=78): compare each other's value through the iteration 的 through 聽起來像 to。
* [2:41](https://youtu.be/TBOPe1dJUhA?si=Q-64rgxN8znpj48g&t=161): NULL 發音錯誤
* 有時候會講完整行要寫的程式碼才開始打字,應該要邊打邊寫,或者要講快一點。(e.g. [5:57](https://youtu.be/TBOPe1dJUhA?si=JLV8iE8p12DHZBSc&t=357))
* [14:16](https://youtu.be/TBOPe1dJUhA?si=mpMpQjTfTbFLPW4k&t=856) 程式碼 trace 錯誤。應該是要跳入 else statement。
## [Easy 83. Remove Duplicates from Sorted List](https://leetcode.com/problems/remove-duplicates-from-sorted-list/description/)
> 銜接問題:[Medium 82. Remove Duplicates from Sorted List II](https://leetcode.com/problems/remove-duplicates-from-sorted-list-ii/description/)
### 測驗說明與問答
🧔: 你會得到一個排序好的鏈結串列,你需要把相同數值的節點移除。
👶: 我確認一下問題,我有一個排序好的 Linked list,如果遇到重複的資料就刪掉,只留一個在 Linked list 裡面。
🧔: 對。
👶: 也就是說,如果我有的 Linked list 長 [1,1,3,4,4] 經過我的 function 後會變成 [1,3,4],請問是這樣嗎?
🧔: 沒錯。
👶: 我的直覺是直接走訪每一個節點,然後拿當前走訪到的節點向後比較,如果相同就直接移除。
```c
struct ListNode* deleteDuplicates(struct ListNode* head){
if( head == NULL) return NULL;
for(struct ListNode *cur = head; cur != NULL; cur = cur->next){
while(cur->next && cur->next->val == cur->val){
cur->next = cur->next->next;
}
}
return head;
}
```
👶: 因為每個節點都需要訪問過,因此複雜度為 $O(n)$
🧔: 看起來沒問題,那如果我連重複的節點本身也要刪除呢?([Medium Leetcode 82.](https://leetcode.com/problems/remove-duplicates-from-sorted-list-ii/description/))
👶: 這樣就要刪除 cur 指向的節點,因此我們需要一個 flag 判斷是否有進入 while 迴圈,同時要將 cur 改為指標的指標。
```c
struct ListNode* deleteDuplicates(struct ListNode* head){
if( head == NULL) return NULL;
for(struct ListNode **cur = &head; *cur != NULL;){
int flag = 0;
while((*cur)->next && (*cur)->next->val == (*cur)->val){
flag = 1;
(*cur)->next = (*cur)->next->next;
}
if (flag){
*cur = (*cur)->next;
}else{
cur = &(*cur)->next;
}
}
return head;
}
```
### 初步檢討
**Interviewer**
* [0:00](https://youtu.be/D7rqwUFOw8M?si=Y2jB4kLP_h6wU78S): 開頭應該避免使用「面試官」這種上對下的詞,可以改成「你好,歡迎來到這次的面試」
* [16:05](https://youtu.be/D7rqwUFOw8M?si=9V85aKPlesukXAIZ&t=965): 結尾有點草率,應該要多說一些話然後在謝謝 interviewee 參加這場面試。
**Interviewee**
* 講話語調太平,我自己聽了都想睡覺了。
* [2:39](https://youtu.be/D7rqwUFOw8M?si=E4fk_bRNjuit2J9T&t=159): NULL 發音錯誤
* [7:02](https://youtu.be/D7rqwUFOw8M?si=CJ0RTZLwb6iJZEp1&t=422): 「離開 while 迴圈」的發音全部連在一起,聽不清楚。
* [14:13](https://youtu.be/D7rqwUFOw8M?si=BBN6qAGTSkycDyqa&t=853): NULL 發音錯誤
## [Medium 146. LRU Cahce](https://leetcode.com/problems/lru-cache/)
### 測驗說明與問答
🧔: 今天會要求你實作一個 LRU cache,總共有 3 個 function 需要你實作。lRUCacheCreate、lRUCacheGet、lRUCachePut。lRUCacheCreate 的功能是初始化 cache 大小,由於我們的 cache 皆是 key、value 成對的,因此 lRUCacheGet 會給定一個數值 key,你需要回傳相對應的 value,最後 lRUCachePut 會給定 key、value,如果資料存在 cache 中就更新 value,如果不在 cache 則加入 cache。
👶: 假如說 cache 的大小是 2,當我先做兩次 put 操作 put(1,1), put(2,2),這時候如果再次進行 put 操作 put(3,3),這時候被移出的資料為 1,1,因為他是最久沒被使用到的資料。
🧔: 對,你可以假設 cache 的 capacity 至少為 1。
👶: 我直覺的作法是使用一個鏈結串列,如果有對資料操作就將資料放到鏈結串列的開頭,這樣當 cache 放滿資料時,串列的最尾端的節點就會是需要被移除的節點。
```c
struct Node{
int key, val;
struct Node *next;
};
typedef struct {
int cap, size;
struct Node *head;
} LRUCache;
LRUCache* lRUCacheCreate(int capacity) {
LRUCache *cache = malloc(sizeof(LRUCache));
cache->cap = capacity;
cache->size = 0;
cache->head = NULL;
return cache;
}
void move_to_head(LRUCache *obj, struct Node **cur){
struct Node *tmp = *cur;
*cur = (*cur)->next;
tmp->next = obj->head;
obj->head = tmp;
return;
}
int lRUCacheGet(LRUCache* obj, int key) {
struct Node **cur = &obj->head;
for (; *cur; cur = &(*cur)->next){
if ((*cur)->key == key){
move_to_head(obj, cur);
return obj->head->val;
}
}
return -1;
}
void lRUCachePut(LRUCache* obj, int key, int value) {
struct Node **cur = &obj->head;
struct Node **prev = NULL;
for (; *cur; prev = cur, cur = &(*cur)->next){
if ((*cur)->key == key){
(*cur)->val = value;
move_to_head(obj, cur);
return;
}
}
if (++obj->size > obj->cap){
free(*prev);
*prev = NULL;
obj->size--;
}
struct Node *new = malloc(sizeof(struct Node));
new->key = key;
new->val = value;
new->next = obj->head;
obj->head = new;
return;
}
```
👶: 最差的情況下複雜度為 $O(n)$,因為在比對 key 的時候需要一個節點一個節點慢慢往後比較。
🧔: 由於我們要實作 cache,複雜度還是希望可以控制在 $O(1)$,請問你會如何修改你的程式。
👶: 我會使用 hash 的方式,同時使用環狀鏈結的方式,這樣查找與移除節點都可以在 $O(1)$ 完成。
```c
struct Node{
int key, val;
struct Node *next, *prev;
struct Node *hnext;
};
typedef struct {
int cap;
int size;
struct Node head;
struct Node **table;
} LRUCache;
LRUCache *lRUCacheCreate(int capacity){
LRUCache *new = malloc(sizeof(LRUCache));
new->cap = capacity;
new->size = 0;
new->head.next = new->head.prev = &new->head;
new->head.hnext = NULL;
new->table = calloc(capacity, sizeof(struct Node *));
return new;
}
void move_to_head(LRUCache *cache, struct Node *cur){
cur->next->prev = cur->prev;
cur->prev->next = cur->next;
cur->next = cache->head.next;
cur->prev = &cache->head;
cache->head.next->prev = cur;
cache->head.next = cur;
return;
}
int hash(int cap, int key){
return key % cap;
}
struct Node *hashget(LRUCache *cache, int key){
struct Node *tmp = cache->table[hash(cache->cap, key)];
for (; tmp && tmp->key != key; tmp = tmp->hnext)
;
return tmp;
}
int lRUCacheGet(LRUCache *cache, int key){
struct Node *n = hashget(cache, key);
if (n){
move_to_head(cache, n);
return n->val;
}
return -1;
}
void add_node(LRUCache *cache, struct Node *new){
struct Node **tmp = &cache->table[hash(cache->cap, new->key)];
for (; *tmp; tmp = &(*tmp)->hnext)
;
*tmp = new;
new->next = cache->head.next;
new->prev = &cache->head;
cache->head.next = cache->head.next->prev = new;
cache->size += 1;
return;
}
void del_tail(LRUCache *cache){
struct Node *tail = cache->head.prev;
tail->prev->next = tail->next;
tail->next->prev = tail->prev;
struct Node **tmp = &cache->table[hash(cache->cap, tail->key)];
for (; *tmp && (*tmp)->key != tail->key; tmp = &(*tmp)->hnext)
;
*tmp = tail->hnext;
cache->size -= 1;
free(tail);
return;
}
void lRUCachePut(LRUCache *cache, int key, int value){
struct Node *n = hashget(cache, key);
if (n){
move_to_head(cache, n);
n->val = value;
return;
}
struct Node *new = malloc(sizeof(struct Node));
new->key = key;
new->val = value;
new->next = new->prev = new->hnext = NULL;
add_node(cache, new);
if (cache->size > cache->cap){
del_tail(cache);
}
return;
}
```
### 初步檢討
**Interviewer**
* 題目選擇錯誤,導致面試時間拖太長。
* [0:00](https://youtu.be/nuNs1ZMWhic?si=Eht1arMG9X6eqG8c): 開頭應該避免使用「面試官」這種上對下的詞,可以改成「你好,歡迎來到這次的面試」
* [0:02](https://youtu.be/nuNs1ZMWhic?si=Eht1arMG9X6eqG8c&t=2): 「這次的題目是」可以改成「這裡有一個 Cache 實作想要跟你討論」
* [27:52](https://youtu.be/nuNs1ZMWhic?si=RU2voS2JZRttyf7n&t=1672): 沒有發現程式碼錯誤。
**Interviewee**
* node 可以使用「節點」來代替;circular linked list 可以使用「環狀鏈結串列」代替,避免中英頻繁交錯。
* [5:19](https://youtu.be/nuNs1ZMWhic?si=zxgcLczIBcu0KwOt&t=319): NULL 發音錯誤
* [20:40、20:45](https://youtu.be/nuNs1ZMWhic?si=5LFr-sZf2aCZiJow&t=1240): 「取值」發音發成「取址」
* [24:13](https://youtu.be/nuNs1ZMWhic?si=WDt_hLl0lxMeyBkH&t=1453): 「把 next 跟 prev 維護成 circular linked list」說法怪怪的,不應該是「維護」。應該說「把 next 跟 prev 當作 circular linked list 的指標」。
* [27:52](https://youtu.be/nuNs1ZMWhic?si=RU2voS2JZRttyf7n&t=1672): 程式碼錯誤,在 `move_to_head` 之後應該要將 value 進行修改。在後續的檢查過程也沒有檢查到。
* [32:21](https://youtu.be/nuNs1ZMWhic?si=QBj6znsuXRAj5hLN&t=1941): 停頓太久
* [37:15](https://youtu.be/nuNs1ZMWhic?si=lpFAM4gY4KbmI7vH&t=2235): 「那個 hash table 位址的 address」意思不明確,應該要說「那個 hash 後對應到的 pointer 的 address」
* [39:12](https://youtu.be/nuNs1ZMWhic?si=BXwm1TgcQ8eK5Cd5&t=2352): 發現講錯後應該要說「我更正一下」
* [47:39](https://youtu.be/nuNs1ZMWhic?si=eWZcOaCGggv8K03X&t=2859): 應該是要說「circular linked list」但實際聽不太出來到底在講什麼。
* 後面講話停頓越來越多,停頓的方式讓人想睡覺。
---
## 第二次作業 - 他評01
### for interviewer
- [ ] 優點
- 說明清楚
- 語速適中
- [ ] 可改進的地方
- 開頭應該避免使用「面試官」這種上對下的詞,可以簡單跟interviewee打招呼,並簡述流程即可
- 避免直接使用Leetcode題目原型,可以加以包裝,例如為什麼要刪除重複的節點?
### for interviewee
- [ ] 優點
- coding很流暢
- [2:54](https://youtu.be/PoYD9bZQCsM?si=TX8zCnQIw0e2MB3c&t=174) 有適度手勢,肢體語言恰到好處
- coding後有舉例加以驗證,確實做到Test
- [ ] 可改進的地方
- 咬字有時會較含糊,如 [5:03](https://youtu.be/PoYD9bZQCsM?si=OqPjSfMUA6HPjViW&t=303) 在說明時,部分地方語調較低時較難聽懂
- 整體語調較平淡
## 第二次作業 - 他評02
### for interviewer
- [ ] 優點
- 關於[Easy 83. Remove Duplicates from Sorted List](https://leetcode.com/problems/remove-duplicates-from-sorted-list/description/)這題,有更進一步討論medium的變形,而且用不大的更動就實作出來
- [ ] 可改進的地方
- 關於[Easy 83. Remove Duplicates from Sorted List](https://leetcode.com/problems/remove-duplicates-from-sorted-list/description/)這題,可以多討論一點,像是如果一開始給的linked list沒有排序的話,interviewee會怎麼想,不一定要實作出code,但是可以討論一下interviewee會怎麼做,例如建hash table看有無重複,或是interviewee就是直接排序,然後重複利用這題所寫的code
### for interviewee
- [ ] 優點
- coding很順,講話蠻穩的
- [ ] 可改進的地方
- 沒啥問題
## 第二次作業-他評03
### 針對 Interviewer 的檢討:
* 可以包裝一下題目
* 可以和 Interviewee 多一些互動,Interviewee 寫完程式碼後,可以給予一些回饋
### 針對 Interviewee 的檢討:
* 打完程式碼後有落實 Test 很棒!
* 有主動提及時間複雜度
* 確認題目時有帶一遍例子確認自己的理解,在寫程式碼前也有先闡述自己的做法很棒!
* Problem 2 有做 follow up 很棒!
* [44:09](https://youtu.be/nd9EUHtTjdM?t=2649)可以帶實際測資,不然很像是再把程式碼說一遍的感覺
## 第二次作業-他評04
**interviewer**
* 最後優化完程式之後可以再針對程式碼進行測試,並且可以跟 interviewee 多一點問答,像是延伸題目、程式碼效能如何等等。
**interviewee**
- [ ] 優點
* 有利用範例去講解自己的邏輯。
- [ ] 可改進的地方
* 音量可以再大聲一點。
* Merge Two Sorted List 中在講解程式碼的時候可以適當的寫註解,輔助敘述。
* 舉例講解的時候可以把舉例放在程式碼裡面註解,這樣講解的時候就不用一直上下滑動滑鼠。