# 2023 年「[資訊科技產業專案設計](https://hackmd.io/@sysprog/info2023)」作業 1 > 暱稱: 呆呆獸 Slowpoke > 👨‍💼: Interviewer > 🙋‍♂️: Intreviewee ## [**27. Remove Element (Easy)**](https://leetcode.com/problems/remove-element/?envType=study-plan-v2&envId=top-interview-150) > [錄影](https://youtu.be/Sh17ndeEbgY) ### 模擬面試過程 👨‍💼: 今天的面試,我們希望你能夠實作 Remove Element,這個算法可以幫助我們快速地清理數據,我們會給你一段整數 array 跟一個指定的整數 K,希望你能將所有等於 K 的值排列在 array 的最前面,並回傳不等於 K 的數量,array 內的其他數值以及 array 的大小並不重要。要注意的是,所有動作必須要 in-place。 👨‍💼: Array 的長度為 0~100,數值大小介於 0~50。 🙋‍♂️: 我想舉個例子確認一下題意。 ``` cpp Input: nums = [1,3,2,2,3], K = 3 Output: 3, nums = [1,2,2,_,_] ``` 🙋‍♂️: 請問這個例子正確嗎? 👨‍💼: 沒有問題。 🙋‍♂️: 我的想法是使用一個 for 迴圈去看過每一個數值,如果不等於 K,就會把他往前放,所以我會再添加一個指標來記錄上次往前放的位置。 👨‍💼: 了解,你可以開始進行實作了。 ``` cpp int removeElement(vector<int>& nums, int K) { int ptr = 0; for (int i=0; i<nums.size(); i++){ if (nums[i] != K){ nums[ptr] = nums[i]; ptr++; } } return ptr; } ``` 🙋‍♂️: 由於只有一層 for 迴圈,所以時間複雜度為 $O(N)$。 👨‍💼: 程式碼沒問題。 👨‍💼: 但你的程式看似有一些多餘的步驟,比如在你的舉例當中,1 會寫入 1 自己的位置,你有什麼解決辦法嗎? 🙋‍♂️: 那我另一個指標應該要從 array 的後面開始找,如果前面的指標遇到等於 K 的數字,就要跟後面指標交換,接著更新指標位置,直到前後指標重合。 ``` cpp int removeElement(vector<int>& nums, int K) { int i=0; int j = nums.size()-1; while (i<=j){ if (nums[i] == K){ nums[i] = nums[j]; j--; } else{ i++; } } return j+1; } ``` 👨‍💼: 看起來沒什麼問題,我們再舉個例子測試一下。 ``` cpp Input: nums = [1], K = 1 Output: 0, nums = [_] ``` 👨‍💼: Okey,沒有問題。 👨‍💼: 但在現實情境中,有時候我們會希望資料經過刪除後,同時資料所占用的空間也變少。對應到這個題目,我們希望在 Remove Element 後, capacity 也可以降低,你會怎麼修改呢? 🙋‍♂️: 我會使用 C++ 中 copy 跟 swap 的特性來完成,首先將原本的 array 複製一份到新的空間,這個步驟配置剛剛好的容量,接下來使用 swap 來改變記憶體位置。 ``` cpp int removeElement(vector<int>& nums, int val) { int i=0; int j = nums.size()-1; while (i<=j){ if (nums[i] == val){ nums[i] = nums[j]; j--; } else{ i++; } } int result = j+1; nums.erase(nums.begin()+result, nums.end()); vector<int>(nums).swap(nums); return result; } ``` ## [**15. 3Sum (Medium)**](https://leetcode.com/problems/3sum/?envType=study-plan-v2&envId=top-interview-150) > [錄影](https://youtu.be/O28WIYcjKZw) ### 模擬面試過程 👨‍💼: "3Sum" 是一個經典的算法問題,稍加改良後,能夠應用在資料庫查詢或金融交易分析中。在這個問題中,輸入會是一串整數,我們希望找出所有不重複的三個數字組合,使它們的總和等於零,請注意你的答案不能有重複的組合。 👨‍💼: 總共有 3 ~ 3000 個數值,數值的大小介於 $-10^5$ ~ $10^5$ 🙋‍♂️: 請問 input 和 output 的資料型態, 我可以假設是 C++ 中的 vector 嗎? 👨‍💼: 可以這樣假設。 🙋‍♂️: 好的,為了更好的理解題目,我想舉個例子確認一下。 ``` cpp 輸入: [-1,0,1,2,-1,-1,-1,2] 輸出: [[-1,-1,2],[-1,0,1]] ``` 🙋‍♂️: 請問這樣有符合題意嗎? 👨‍💼: 這樣有符合。 🙋‍♂️: 那如果找不到符合條件的解,是不是回傳空的 vector 即可? 👨‍💼: 對。 🙋‍♂️: 我最初步的想法,是使用三層迴圈去找到所有解,並使用集合去儲存這些解以避免重複,不過這個演算法的時間複雜度是 $O(N^3)$,非常沒有效率,所以我會想使用雙指標來解決這個問題。首先,先對所有數值做升冪排序,接著使用一層 for 迴圈,每次固定一個值,剩下兩個值使用雙指標去選出來。 👨‍💼: 了解,你可以開始進行實作了。 ``` cpp vector<vector<int>> threeSum(vector<int>& nums) { int length = nums.size(); set<vector<int>> result_set = {}; sort(nums.begin(), nums.end()); for (int i=0; i<length-2; i++){ int left = i+1; int right = length -1; while (left < right){ if ((nums[i] + nums[left] + nums[right]) > 0) { right--;} else if ((nums[i] + nums[left] + nums[right]) < 0) {left++;} else { result_set.insert({nums[left], nums[i], nums[right]}); left++; } } } vector<vector<int>> result(result_set.begin(), result_set.end()); return result; } ``` 🙋‍♂️: 排序的時間複雜度為 $O(N\log N)$,迴圈跟雙指標的時間複雜度為 $O(N^2)$,所以這個演算法的時間複雜度為 $O(N^2)$。 👨‍💼: 你目前的演算法有用到集合,插入元素的時候,會需要維護的成本,如果不要使用集合,你有什麼想法嗎? 🙋‍♂️: 有的,重複的組合是由於相同的數值被選擇多次造成的,我們可以利用排序過後的特性,當連續出現相同數值時,可以直接跳過,來避免選到重複的組合。 ``` cpp vector<vector<int>> threeSum(vector<int>& nums) { int length = nums.size(); vector<vector<int>> result = {}; sort(nums.begin(), nums.end()); for (int i=0; i<length-2; i++){ int left = i+1; int right = length -1; while (left < right){ if ((nums[i] + nums[left] + nums[right]) < 0) {left++;} else if ((nums[i] + nums[left] + nums[right]) > 0) { right--;} else { result.push_back({nums[left], nums[i], nums[right]}); while (left < right && nums[left] == nums[left+1]) {left++;} while (left < right && nums[right] == nums[right-1]) {right--;} left++; } } while (i < length-2 && nums[i] == nums[i+1]) {i++;} } return result; } ``` ## [**637. Average of Levels in Binary Tree (Easy)**](https://leetcode.com/problems/average-of-levels-in-binary-tree/?envType=study-plan-v2&envId=top-interview-150) > [錄影](https://youtu.be/W66rCiuSKDk) ### 模擬面試過程 👨‍💼: Now I want to discuss a question about binary trees, "Average of Levels in Binary Tree" The number of nodes in the tree is in the range [1, 100]. Please follow the definition below for the structure of the binary tree. ``` cpp // Definition for a binary tree node. struct TreeNode { int val; TreeNode *left; TreeNode *right; }; ``` 🙋‍♂️: Could the node values be float? 👨‍💼: Let's assume they are integers. 🙋‍♂️: Okey. I want to provide an example to help me understand the question. ```graphviz digraph graphname{ node[shape=circle] { A[label=3] B[label=9] C[label=20] D[label=NULL, shape=plaintext] E[label=NULL, shape=plaintext] F[label=15] G[label=7] } A->B;A->C; B->D;B->E; C->F;C->G; } ``` 🙋‍♂️: In this example, I need to return [3, 14.5, 11]. Did I get it right? Even though the input values are integers, the output values might be float. 👨‍💼: Yes, your understanding is right. 🙋‍♂️: For this question, my initial idea is to use DFS to visit each node and use a table to keep track of the total sum and the number of nodes at each level. Finally, we can calculate the average. ``` cpp void dfs(TreeNode* root, int level, vector<pair<double, int>>& sum_levels) { if (!root) return; int k = root->val; if (sum_levels.size() <= level) { sum_levels.push_back({k,1}); } else{ sum_levels[level].first += k; sum_levels[level].second++; } dfs(root->left, level+1, sum_levels); dfs(root->right, level+1, sum_levels); } vector<double> averageOfLevels(TreeNode* root) { vector<pair<double, int>> sum_levels; vector<double> result; dfs(root, 0, sum_levels); for (int i=0; i<sum_levels.size(); i++){ double sum = sum_levels[i].first; int num = sum_levels[i].second; result.push_back(sum/num); } return result; } ``` 🙋‍♂️: Since DFS visits each node only once, the time complexity is $O(\lvert V\rvert)$, and the space complexity is also $O(\lvert V\rvert)$ because, for imbalanced trees, the depth of recursion could be equal to the number of nodes. 👨‍💼: It looks good, but in some cases, I might only want to calculate the average for a specific level. With your method, I would still have to traverse the entire tree. Do you have a way to improve this issue? 🙋‍♂️: I want to use BFS to visit each node layer-by-layer and calculate the average for nodes on the same level. This way, we can compute averages in the order we want. ``` cpp vector<double> averageOfLevels(TreeNode* root) { vector<double> result; queue<TreeNode*> node_queue; node_queue.push(root); while (!node_queue.empty()) { double sum = 0; int size = node_queue.size(); for (int i=0; i<size; i++) { TreeNode* node = node_queue.front(); sum += node->val; node_queue.pop(); if (node->left != NULL) {node_queue.push(node->left);} if (node->right != NULL) {node_queue.push(node->right);} } result.push_back(sum/size); } return result; } ``` 🙋‍♂️: The time complexity and space complexity are both $O(\lvert V\rvert)$. This is because BFS visits each node only once, and in the worst case, all nodes might be stored in the queue. ### 總體檢討 * 說話太糊,咬字不清楚,需要平常多注意。 * 聲音太小,語氣不夠肯定。 * 對話中太多 "嗯",要改 * 英語口說要加強 - [ ] 針對 interviewer 的檢討 * 可以針對程式碼,進行更多的測試。 - [ ] 針對 interviewee 的檢討 * 打程式碼的時候,應該要配合階段性解說,讓 interviewer 更加清楚程式碼的脈絡。 * 會有過長的沉默時間,應蓋要盡量的講出 "預計要做" 或 "正在做" 的事。 --- ## 他評01 - [ ] 優點 * 聲音柔和 * 第一題interviewer指出interviewee程式碼的問題並請其改正,有良好的互動性。 * 說明自己的想法後再開始實作。 * interviewee提出問題增進和interviewer的互動性。 - [ ] 可改進的地方 * 語氣可以更強這樣感覺起來更有自信。 * 聲音和背景音融合在一起,不容易聽清楚,也許說大聲點或是語氣有抑揚頓挫會更好。 * 那個打字一直出現的「拍噠」聲音聽起來好燥== * 打程式碼的時候可以邊打邊說明程式碼的用途,不然好安靜。 * 第二題可以不用說出3sum,直接進入情境題。 ## 他評02 ### Interviewer - [ ] 優點 * 說明題目很清楚 * 英文口說不錯 - [ ] 可改進的地方 * 也許一開始就可以說明情境,把完整的題意更清楚的說明。 ### Interviewee - [ ] 優點 * 在寫程式過程中,思考蠻清楚的。只要配上解釋應該就會好很多 * 寫程式中其實都沒卡頓,我認真覺得蠻厲害的 * 英文口說蠻好的 * 覺得可以再把整個思路講得更清楚一些 * 程式看起來很簡潔明瞭 - [ ] 可改進的地方 * 在撰寫程式碼過程中可以邊說明,不然過多留白會蠻尷尬的 * 可以跟面試官討論一下是否可以用C++ * [10:02](https://youtu.be/Sh17ndeEbgY?si=5wrniHEYgYDjDjx5&t=602): 為什麼Erase過後還要重新配置空間呢? C++的erase是真的會把size縮小的吧? 如果我理解錯誤有沒有參考資料plz。 Sundew [這是我的認知](https://www.geeksforgeeks.org/vector-erase-and-clear-in-cpp/)。[查資料又查到一個很有趣的](https://stackoverflow.com/questions/347441/erasing-elements-from-a-vector) 這邊小弟去實驗stackoverflow std::remove+vector.erase 的結果 ![](https://hackmd.io/_uploads/BkFIB5al6.png) * 第二部[2:58](https://youtu.be/O28WIYcjKZw?si=w5bl14quGE-3CyW8&t=178): 也許講話要大聲一點可能比較清晰? 我自己背景也會有噪音所以是有後製把噪音去掉,看有沒有人有推薦麥克風,可以改善雜音太多的問題,我是用Iphone耳機 * 第三部[0:26](https://youtu.be/W66rCiuSKDk?si=4MT0MY0l-2W49Skq&t=26): 題目上已經給int了 還是意思是return value.. ## 他評03 ### Interviewer - [ ] 優點 * 提問的時候有指出需要改進的地方。 * 英文口說咬字很清晰。 - [ ] 可改進的地方 * 文字可以稍微大一點。 * 提問環節時互動可以再多一點。 ## Iterviewee - [ ] 優點 * 程式撰寫的時候很流暢。 - [ ] 可改進的地方 * 可以在邊撰寫程式邊說明,或是在寫某一行條件時候 (像是 while 的中止條件) 可以先講解後再寫。 * DFS 的時間複雜度,可以說明 $V$ 的定義是節點數。 ## 他評04 ### Interviewer - [ ] 優點 * 清楚說明出題目的與可應用的地方 * 面試互動完整 - [ ] 可改進的地方 * 可以將題目直接套入情境之中 * 可以多給予點題目的解釋 ### Iterviewee - [ ] 優點 * 遵循Reacto,與面試官互動良好 * 程式撰寫流暢 * 時間複雜度分析清楚 - [ ] 可改進的地方 * 程式撰寫時空白時間太長,宜適當加入註解與說明 * 第三題主體想法是DFS,可以先從這部分先進行撰寫與說明 ## 他評05 ### interviewer - [ ] 優點 * 有使用情景為什麼需要做這個東西 * 有另外舉一個input測試interviewee - [ ] 可改進之處 * 如果interviewee寫程式走火入魔了完全沒解說,可以主動提出一些問題打破尷尬 ### interviewee - [ ] 優點 * 有使用例子與interviewer確認自己的理解是否正確 * 有先用較差的時間復雜度再優化 - [ ] 可改進之處 * 沉默時間太長,在coding的時候應該伴隨著講解 ## 他評06 ### interviewer - [ ] 優點 * 舉出實作上可以應用的地方很棒。 * 向interviewee提出問題,交流是否有地方需要改進,互動良好。 - [ ] 可改進之處 * 有時候會有一小段字突然糊掉、聽不清楚。 * 第二題不需要特別說題目名稱關鍵字,這樣刷題者馬上就手癢想寫了XD。 ### interviewee - [ ] 優點 * 對答橋段良好,REACTO完整。 - [ ] 可改進之處 * 一邊寫一邊卻沒有說明,建議在撰寫的時候配合說明,可以清楚讓面試主持人知道在幹嘛。 * 承上,配合在各個程式片段下註解,更可以有效傳達自己的想法。