--- tags: 資訊科技產業專案設計作業 --- # 資訊科技產業專案設計課程作業 2 > 貢獻者: 威爾 will > [模擬面試錄影(漢)](https://youtu.be/Z7pXNUo50No) > [模擬面試錄影(漢)](https://youtu.be/LbsDUHjpxxE) > [模擬面試錄影(英)](https://youtu.be/6FbfxWwfqho) 🧔:interviewer 👶:interviewee ## [230. Kth Smallest Element in a BST](https://leetcode.com/problems/kth-smallest-element-in-a-bst/) ### 測驗說明與問答 🧔:希望 will 可以實作一個 function,輸入一個二元搜索樹(BST),這個 function 可以找出裡面第 k 小的元素。 👶:好的您的問題是給我一個二元搜尋樹,然後我要想辦法找出裡面由小到大排在第 k 位的點的值嗎?: 🧔:是的,沒錯! 👶:那首先對這個問題提出一些案例 ``` Example 1: Input: k = 1 BST: 3 / \ 1 4 \ 2 Output: 1 Example 2: Input: k = 3 BST: 5 / \ 3 6 / \ 2 4 / 1 Output: 3 ``` 👶:另外假設至少有一個點。 👶:我剛開始想到的方法是用遞迴解,以 inorder 的方式走訪整顆二元搜尋樹,因為 inorder 會按照二元搜尋樹的點由小到大走,因此我只需要再走的時候順便做 K 值的計算就可以找到答案 🧔:這樣的時間複雜度是如何? 👶:由於需要遞迴的訪問每一個節點執行函數,故時間複雜度為$O(n)$ ### Kth Smallest Element in a BST ```c= struct TreeNode { int val; struct TreeNode* left; struct TreeNode* right; }; void inorder(struct TreeNode* node, int* k, int* ans) { if (node) { inorder(node->left, k); if (--*k == 0) {*ans = node->val;return;} inorder(node->right, k); } } ``` 🧔:如果這個 BST 很常被更改(插入/刪除),且你也很常需要找到第 k 小的元素該怎麼辦? 👶:我的想法是在 ```struct TreeNode``` 中多加一個成員叫做 ```count``` 用來紀錄這個點的左右子樹的點數再加上自己(1)的總節點數,這麼一來每當 BST 在新增或刪除時也更新 ```count``` 的前提下,時間複雜度可以是$O(h)$,```h``` 代表樹高 ### Follow up ```c= struct tTreeNode{ int val; int count; struct tTreeNode* left; struct tTreeNode* right; }; void kthSmallest(struct tTreeNode* root, int k, int* ans) { int leftcount = root->left ? root->left->count : 0; if (leftcount+1 == k) {*ans = root->val; return;} else if (leftcount+1 > k) kthSmallest(root->left, k, ans); else kthSmallest(root->right, k - leftcount - 1, ans); } ``` ### 同學互評 針對 interviewer 的檢討: [00:00](https://youtu.be/LbsDUHjpxxE) 不要自稱自己是面試官比較好,就説你負責這次的面試。 [00:11](https://youtu.be/LbsDUHjpxxE) 如果提供題目及例子,並稍微講解一下會比較好。不然interviewee 可能會不懂你的問題。 針對 interviewee 的檢討: * 盡量少講語助詞。 * 寫完 code 可以試著代入測資測試。 * 在被問到很常被更改的問題時,提出了更改原本的資料結構的想法,感覺可以的話應該是希望不要動到本來的基本結構,去其他方法會比較好一點。 ## [101. Symmetric Tree](https://leetcode.com/problems/symmetric-tree/) ### 測驗說明與問答 🧔:在第二題我給你一個二元樹,請你檢查這棵樹是否為左右對稱(Symmetric)。 👶:沒問題,所以我會有一棵二元樹,然後我要檢查這棵樹是否左右對稱。 🧔:是的,沒錯 👶:好的那麼要處理這個問題,比如說下面這個例子 ``` 下面這顆是 symmetric tree 1 / \ 2 2 / \ / \ 3 4 4 3 ``` 👶:我一開始想到的方案是利用遞迴的方式來解,比如說看到 root 的左右節點(2, 2)簡稱為左節點跟右節點,這邊可以判斷(1)左節點的左子樹跟右節點的右子樹是否對稱、(2)左節點的右子樹與右節點的左子樹是否對稱,來判斷這顆 BT 是否為對稱樹 👶:另外寫程式時還要注意到子樹是空的的情況 ```c= struct TreeNode { int val; struct TreeNode *left; struct TreeNode *right; }; bool isSYM(struct TreeNode* L, struct TreeNode* R) { if (!L && !R) return true; else if (!L || !R) return false; if (L->val == R->val) { return isSYM(L->left, R->right) && isSYM(L->right, R->left); } else return false; } bool isSymmetric(struct TreeNode* root) { return isSYM(root->left, root->right); } ``` 🧔:好的,那麼接下來想請問有沒有非遞迴的方式 👶:我的想法是用兩個 ```queue```分別存 root 的左右子樹的點,把每次走到的點 pop 出去,然後 push 進它的左右子點。來達成iteration 的解法 👶:舉例來說 ``` 1 queue1 queue2 判斷兩個queue head 有沒有相同 / \ 有就把他們pop掉,然後push他們的左右子點進來 2 2 [2] [2] 這邊要注意到兩個 queue push 左右子點的順序要對稱 / \ / \ 3 N 4 3 [3, NULL] [3, 4] ``` 👶: C 語言因為沒有 queue 結構,所以這邊我要用 linked list 來實作簡單的 queue ```c= struct TreeNode { int val; struct TreeNode *left; struct TreeNode *right; }; struct qnode{ struct TreeNode *node; struct qnode *next; }; void insert(struct qnode *tail, struct TreeNode *tnode) { struct qnode *n = malloc(sizeof(struct qnode)); n->node = tnode; n->next = NULL; tail->next = n; } bool isSymmetric(struct TreeNode* L, struct TreeNode* R) { struct qnode* L_head = &(struct qnode){struct TreeNode* L, NULL}; struct qnode* R_head = &(struct qnode){struct TreeNode* R, NULL}; struct qnode* L_tail = L_head; struct qnode* R_tail = R_head; while (L_head && R_head) { struct TreeNode* L = L_head->tnode; struct TreeNode* R = R_head->tnode; if (!L && !R) { L_head = L_head->next; R_head = R_head->next; continue; } else if (!L || !R) return false; else { if (L->val != R->val) return false; else { insert(L_tail, L->left); L_tail = L_tail->next; insert(L_tail, L->right); L_tail = L_tail->next; L_head = L_head->next; insert(R_tail, R->right); R_tail = R_tail->next; insert(R_tail, R->left); R_tail = R_tail->next; R_head = R_head->next; } } } return true; } ``` ### 同學互評 針對 interviewer 的檢討: * [0:00](https://www.youtube.com/watch?v=Z7pXNUo50No) 題目還是給個example會比較好。 * [4:11](https://www.youtube.com/watch?v=Z7pXNUo50No) 除了問時間複雜度,空間複雜度也可以問一下。 * [0:00](https://www.youtube.com/watch?v=Z7pXNUo50No) 題目講解過於簡潔,避免 interviewee 理解錯誤,或理解太慢。且應該至少給予 true 跟 false 的範例各一個。可以在最後增加敘述「也請自行定義二元樹的結構」之類的話。 * [9:52](https://youtu.be/Z7pXNUo50No?t=590) 「那我大概知道了」像是 interviewer 也不完全了解 interviewee 的作法。若真的有不清的應該要繼續討論問清楚,若都清楚的應該要給更肯定的回應。接著 * 影片結束應該還要再帶給 interviewer 做評論,結束的有點倉促,可以對於第二個實作方式有更進一步的討論。 針對 interviewee 的檢討: * 解説的時候盡量不要花太多次滑鼠。 * [3:56](https://youtu.be/Z7pXNUo50No?t=236) 影片中說的時間複雜度是錯的,不是 $O(h-1)$ 喔,有兩個點要注意,一、減一是不必要的,因為當 h 夠大時,減一可以忽略不計。二、每層要做判斷的數量不同,所以不可能是 $O(h)$。應該從每個節點拜訪的次數分析,時間複雜度是 $O(n)$,$n$ 為節點數量 * [10:18](https://youtu.be/Z7pXNUo50No?t=618) 開始,在講解 BFS 時,不需要刪掉 DFS 的部分,直接在底下實作即可。 * [22:32](https://youtu.be/Z7pXNUo50No?t=1352) 這邊的 continue 是不必要的。 * [27:22](https://youtu.be/Z7pXNUo50No?t=1642) insert 這個 function 可以改成回傳新增的 qnode,這樣在使用時,可以直接用回傳值去更新 tail,程式也會更簡潔。(tail 用 double pointer 去更改也是有相同效果的) example : ```clike! struct qnode* insert(struct qnode* tail, struct Treenode* tnode){ struct qnode* q_node = malloc(sizeof(qnode)); q_node->tnode = tnode; q_node->next = NULL; tail->next = q_node; return q_node; // 回傳 tail 應該要指向的位置 } // 每次使用時可以少打一行 L_tail = insert(L_tail, L->left) ``` ## 同儕檢討(他評) ### [面試檢討 - 他評 01](https://hackmd.io/@sysprog/HJmh9FlGs) * 優點 * 有重複問題和舉例討論。 * 口齒清楚 * 缺點 * 過場的時候可以用字幕標記 interviewer 和 interviewee * [2:20](https://www.youtube.com/watch?v=8o5xDyZW-L0#t=2m20s) interviewee 在說想法時可以搭配打字舉例說明會比較好懂 * [2:52](https://www.youtube.com/watch?v=8o5xDyZW-L0#t=2m52s) interviewer 在這邊可以把題目從 32 bits 改成 8 bits 之類的,可以避免冗長的打程式過程 * [3:20](https://www.youtube.com/watch?v=-MmBAeRkeDc#3m20s) interviewee 可以先問能不能用標準函式庫 ### [面試檢討 - 他評 02](https://hackmd.io/@sysprog/SyxXzTYgGi) * 優點 * 口試清楚,對答流暢 * 缺點 * [0:10](https://www.youtube.com/watch?v=eG_ckTWiW8s#0m10s) 記得不要講面試官 * [0:29](https://www.youtube.com/watch?v=eG_ckTWiW8s#0m29s) interviewer 在說明題目時可以打一些範例來讓 interviewee 更能理解題目 * [0:56](https://www.youtube.com/watch?v=eG_ckTWiW8s#0m56s) interviewee 跳過 repeat,直接進到 approach 的部分了 * [9:28](https://www.youtube.com/watch?v=eG_ckTWiW8s#9m28s) 寫完可以先問問看 interviewer 的意見,之後再試著最佳化,比較有互動感 ### [面試檢討 - 他評 03](https://hackmd.io/Jg0pMfMtSDmUeNjvhOT7wA?both) * 優點 * interviewee 有停頓下來思考,不會讓人誤以為是背答案 * 口齒清楚 * 缺點 * [0:02](https://www.youtube.com/watch?v=Wy88DIP9j1o#0m02s) 記得不要講面試官 * [0:11](https://www.youtube.com/watch?v=Wy88DIP9j1o#0m11s) interviewer 說明題目時可以搭配打字讓 interviewee 更好理解 * [3:54](https://www.youtube.com/watch?v=Wy88DIP9j1o#3m54s) interviewer 提問時不應說能快點嗎,可以提出一個時間複雜度給 interviewee 思考 ### [面試檢討 - 他評 04](https://hackmd.io/@sysprog/BJ_179gfo) * 優點 * Interviewer 說明簡單明瞭,也有用圖來表達範例,讓 interviewee 更好理解 * Interviewee 有先向 interviewer 確認對題目的理解,然後再提出自己的想法 * Interviewee 口齒清楚,表達也很流暢 * Interviewee 有做到在撰寫程式碼的同時講出自己的想法 * 缺點 * Interviewer 可以向 interviewee 討論複雜度,然後再討論有沒有其他的 approach * [2:12](https://www.youtube.com/watch?v=a8EAkMWPkB8#t=2m12s) 不是叫空子樹,要叫做這棵樹為空(空集合) ### [面試檢討 - 他評 05](https://hackmd.io/EIw2WNFWQ2OI0cVfJqRGmA?view) * 優點 * Interviewer 的說明簡單明瞭,也有用圖來表達範例,讓 interviewee 更好理解 * Interviewer 可以向 interviewee 討論複雜度,然後再討論有沒有其他的 approach * Interviewee 有先向 interviewer 確認對題目的理解,然後再提出自己的想法 * Interviewee 有做到在撰寫程式碼的同時講出自己的想法 * 缺點 * [5:16](https://www.youtube.com/watch?v=2614P2z1bg8#t=5m16s) Interviewee,Auto-Complete 記得要關掉 * [9:37](https://www.youtube.com/watch?v=2614P2z1bg8#t=9m37s) Interviewee 可以用快慢指標來最佳化 * Interviewee 打字時盡量不要轉頭