# 2024「[資訊科技產業專案設計](https://hackmd.io/@sysprog/info2024/https%3A%2F%2Fhackmd.io%2F%40sysprog%2Finfo2024-homework1)」HW1
> 櫻木滑倒
> Sakuragi
>🕵️♂️:Interviewer
>🕶️:Interviewee
> [模擬面試錄影-1 (漢)](https://youtu.be/KYOUfnXouMA)
> [模擬面試錄影-2 (漢)](https://youtu.be/4gbTHgfwdnc)
> [模擬面試錄影-3 (英)](https://youtu.be/3szsZ7jYolg)
## LeetCode 69. [Sqrt(x)](https://leetcode.com/problems/sqrtx/description/) : Easy
### 模擬面試過程
🕵️♂️: 你好,我是Jack,我是任職於Banana公司的面試官。
🕵️♂️: 我給你Google Doc的連結之後就可以開始了。
🕵️♂️: 在電腦圖學中,無論是在構建渲染環境還是設計遊戲,都經常需要計算物體之間的距離,而這種計算往往涉及平方根的運算。
🕵️♂️:假設今天給你一個介於 $0$ 到 $2^{31}-1$ 的數值$x$,並要求在不使用任何函式庫的情況下,我們想求出$x$的平方根。
🕵️♂️:請問你對題目有疑問嗎?
🕶️:有,我想請問他的平方根不是整數的情況下如何表示答案?
🕵️♂️:往下取整數就可以了。
🕶️:好的,爲了確認我對題目的理解,我想用一個例子説明。
輸入: `10`
輸出: `3`
🕶️:請問我有理解錯題目嗎?
🕵️♂️:沒有,你可以開始寫你的程式了。
🕶️:在寫程式之前,我想先講一下我的想法。
🕶️:最直接的解法是使用一個迴圈,從輸入的值開始依次遞減,利用decision判斷當前數字的平方是否小於或等於輸入值。如果符合這個條件,則該數字就是輸入值的平方根。
---
### 暴力解法
```cpp
class Solution {
public:
int mySqrt(int x) {
for(int i = x-1; i > 0; i--){
int decision = i*i;
if(decision <= x){
return i;
}
}
return 0;
}
};
```
---
🕵️♂️:你用一個迴圈把所有可能的答案都判斷一次是可以解決我們的題目。但以實務層面來説,輸入資料十分大時,程式的效能會很差。請問你有其他更優秀的解法嗎?
🕶️:確實,之前的解法時間複雜度是 $O(x)$。
🕶️:我的想法是利用二分搜尋法來解,這樣可以大幅度降低迴圈次數,將時間複雜度降到 $O(\log x)$。具體來說,二分搜尋法的思路是每次將搜索範圍縮小一半,直到找到接近或等於輸入值平方根的整數。
🕶️:一開始判斷輸入是0或1的狀況,接下來開始實作二分搜尋法,最後再用一個判斷式來處理往下取整數的情況。
---
### 二分搜尋法
```cpp
class Solution {
public:
int mySqrt(int x) {
if(x == 1 || x == 0) {
return x;
}
int left = 1, right = x - 1;
while(left < right){
int mid = (left + right) / 2;
int decision = mid * mid;
if(decision == x){
return mid;
}
else if(decision < x){
left = mid + 1;
}
else if(decision > x){
right = mid - 1;
}
}
if(left == right){
return left-1;
}
return 0;
}
};
```
---
🕵️♂️:非常好,如果我們公司今天需要開發一款遊戲,且需要處理大量的平方根運算,而數值範圍是有限的,請問有沒有更快的解決方案呢?
🕶️:如果數值範圍是有限的,我們可以使用雜湊表來最佳化演算法。將系統中可能用到的平方根事先計算並存入雜湊表中,這樣在需要計算平方根時就可以直接查表,從而實現 $O(1)$ 的時間複雜度。
🕵️♂️:非常好,櫻木滑倒先生。我們會再寄第二封的面試通知到你的信箱,祝你好運。
🕶️:非常感謝你,Jack先生。
---
### 初步檢討
- [ ] 針對 interviewer 的檢討:
* 講話太卡
* 咬字清晰點
* 少用助語詞(痾、然後)
* 可以更深入對演算法的空間複雜度做討論
* 應該對程式碼提出質問(因爲沒有測試過)
- [ ] 針對 interviewee 的檢討:
* 講話太卡
* 咬字清晰點
* 打程式碼中剪輯太多次
* 少用助語詞(痾、然後)
* 打程式有錯字
* 解釋程式碼須更清楚
* 視訊要照到鍵盤
### 第二次作業-他評 01
- [ ] 針對 interviewer 的檢討:
* [0:02](https://youtu.be/KYOUfnXouMA?t=2): 不該自稱是「面試官」,這有「上對下」的隱含意思,大家日後可能會是同事,而且說不定應徵者一開始的職等就比自己高^台灣很小,你永遠很難想像記仇會多久^,interview 是得知「相互」的「觀點」的途徑,不是讓你施展官威的場合。可改說「感謝你的時間,接下來由我主持這場面試」或「你好,從我手上這份簡歷,我感受到您對程式設計的熱情和專業,但在我們談及工作內容前,我想幫公司同仁先認識你在程式開發的想法和風格」
* [0:09](https://youtu.be/KYOUfnXouMA?t=9): 注意 render 的翻譯,參見〈[資訊科技詞彙翻譯](https://hackmd.io/@sysprog/it-vocabulary)〉
* [0:16](https://youtu.be/KYOUfnXouMA?t=16): 在線性代數和工程數學中,相比單純的開平方根,常見 [Frobenius Norm](https://youtu.be/Gt56YxMBlVA) (還記得 SVD 分解嗎?),不過在 LeetCode 原題中,使用整數開平方根,其應用領域就受限,於是該有其他案例。
* [0:24](https://youtu.be/KYOUfnXouMA?t=24): 不用特別說「請問你有什麼疑問嗎?」這題在沒有變形的狀況下,不是難題,應該及早開始,可改說「請留意整數範圍限制,並在不使用任何函式庫的前提,進行程式開發,過程中我會斟酌加上一些限制」
- [ ] 針對 interviewee 的檢討:
* [1:19](https://youtu.be/KYOUfnXouMA?t=79): 避免說「我想講一下」這種話,無助於溝通,用精簡的話 (注意未來可能是英語) 來確認和提問。
* [1:35](https://youtu.be/KYOUfnXouMA?t=95): 避免說「如果有符合的話」,這顯得你很沒信心,可改說「就我所知」,或者直接推進 REACTO。以簡單題目開始,後續可能會有延伸題目,應該把時間保留在更多可展現自身經驗之處。
* [1:51](https://youtu.be/KYOUfnXouMA?t=111): 不用將 `public` 這樣的關鍵字 (keyword) 唸出來,專注在程式碼的本體
* [2:05](https://youtu.be/KYOUfnXouMA?t=125): 在 REACTO 的 Repeat 和 Approach 階段已有說明用的範例數值 (即 3 vs. 10) 和流程,稍早應該邊說邊紀錄,在 Coding 階段就可以重用,這樣不僅思緒更連貫,程式碼不用完整書寫,亦可增進討論
* [2:31](https://youtu.be/KYOUfnXouMA?t=151): 在迴圈內有大量的乘法運算,在還沒開始 Coding,應當就討論如何減少運算,例如 $sqrt(1)$, $sqrt(2)$, $sqrt(3)$ 沒有運算的必要,直接檢查輸入 x 的數值,當 $x < 4$ 就返回 $1$
* [2:47](https://youtu.be/KYOUfnXouMA?t=167): 變數 `decision` 的命名不精準,劍橋詞典的[解釋](https://dictionary.cambridge.org/dictionary/english/decision): "a choice that you make about something after thinking about several possibilities"
* [3:45](https://youtu.be/KYOUfnXouMA?t=228): 用「改進」取代「優化」,參見〈[資訊科技詞彙翻譯](https://hackmd.io/@sysprog/it-vocabulary)〉,當提及二分搜尋法的時候,要有其效益分析 (時間複雜度、計算次數的比較),避免說「大幅降低」這樣不精準的話語,理工人要用證據說話。
* [4:24](https://youtu.be/KYOUfnXouMA?t=264): 避免說「我要開始打程式」這種顯而易見的話語。
* 考慮〈[從 √2 的存在談開平方根的快速運算](https://hackmd.io/@sysprog/sqrt)〉
---
## 119. [Pascal's Triangle II](https://leetcode.com/problems/pascals-triangle-ii/description/) : Easy
### 模擬面試過程
🕵️♂️: 你好,我是Jackson,我是任職於Grape公司的面試官。
🕵️♂️: 我給你Google Doc的連結之後就可以開始了。
🕵️♂️: 巴斯卡三角形能計算二項式係數,對於二項式展開至關重要。然後二項式展開在演算法的設計和機率統計中也有廣泛應用。
🕵️♂️:假設給定一個介於 0 到 33 之間的數字 rowIndex,表示巴斯卡三角形的第 rowIndex 層,最上方為第 0 層,我們的目標是求出該層的所有二項式係數。
🕵️♂️:請問你對題目有疑問嗎?
🕶️:沒有,但是爲了確認我對題目的理解,我想用一個例子説明。
輸入: `rowIndex = 3`
輸出: `[1,3,3,1]`
🕶️:請問我對於題目的理解上有任何錯誤嗎?
🕵️♂️:沒有,你可以開始寫你的程式了。
🕶️:在寫程式之前,我想先講一下我的想法。
🕶️:我的想法是先判斷初始條件,當輸入為 0 或 1 時,直接返回對應的結果。如果輸入大於 1,則使用雙重迴圈來計算巴斯卡三角形的值。第一層迴圈負責根據目標層數逐層更新,每次計算時創建一個 temp 來存放新一層的值。第二層迴圈負責根據目前的層數,兩兩相加計算巴斯卡三角形的係數,並將結果存入 temp。雙重迴圈完成後,將計算結果賦值給 answer。
🕶️:這樣的解法由於使用了雙重迴圈,因此時間複雜度為 $O(rowIndex^2)$。同時,使用了 vector,最終會存放 rowIndex + 1 個元素,其他變數皆為常數級別,因此空間複雜度為 $O(rowIndex)$。
---
### 暴力解法 ( <s>雙重迴圈</s> )
:::danger
下方程式碼的迴圈並非「對等」,不是「雙」。改稱「二層迴圈」。
:::
```cpp
class Solution {
public:
vector<int> getRow(int rowIndex) {
vector<int> answer = {1};
if (rowIndex == 0){
return answer;}
answer.push_back(1);
for(int x = 2; x <= rowIndex; x++){
vector<int> temp = {1};
for(int y = 0; y < x-1 ; y++){
int insert = answer.at(y) + answer.at(y+1);
temp.push_back(insert);
}
temp.push_back(1);
answer = temp;
}
return answer;
}
};
```
---
🕵️♂️:做得非常好。但有個小問題,以實務層面來説,輸入資料十分龐大時,程式的效能會很差。請問你可以提出更優秀的演算法嗎?
🕶️:沒錯,之前的解法時間複雜度與 rowIndex 的平方成正比。
🕶️:我們可以直接利用二項式係數的性質來計算每一行的元素,以達到 $O(rowIndex)$ 的時間複雜度。
🕶️:具體的公式如下: $$C(n, k) = \frac{n!}{k!(n-k)!}$$
🕶️:巴斯卡三角形第 n 行的每個元素是依據二項式係數直接計算出來的。
🕶️:我們這邊也使用到二項式係數的遞迴公式來計算目標層的每個元素:
$$
C(n, k) = C(n, k-1) \times \frac{n - k + 1}{k}
$$
🕶️:這個演算法一樣使用了一個大小為 rowIndex + 1 的 vector 來存儲結果,因此空間複雜度仍然是 O(rowIndex)。
---
### 二項式係數
```cpp
class Solution {
public:
vector<int> getRow(int rowIndex){
vector<int> answer(rowIndex + 1, 1);
for(int i = 1; i < rowIndex; i++){
answer[i] = answer[i-1] * (rowIndex - i + 1) / i;
}
return answer;
}
};
```
---
🕵️♂️:非常好,櫻木滑倒先生。我們會再寄第二封的面試通知到你的信箱,祝你好運。
🕶️:十分感謝你,Jackson先生。
---
### 初步檢討
針對 interviewer 的檢討:
- 講話太卡
- 咬字清晰點
- 少用助語詞(痾、然後)
- 可以更深入對演算法的空間複雜度做討論
- 應該對程式碼提出質問(因爲沒有測試過)
- 題目出得不夠好(優化的演算法只是公式,意義不大)
針對 interviewee 的檢討:
- 講話太卡
- 咬字清晰點
- 打程式碼中剪輯太多次
- 整個程式碼要打在同一頁(要調整Google Doc比例)
- 少用助語詞(痾、然後)
- 打程式有錯字
- 解釋程式碼須更清楚
- 二項式遞迴公式須解釋更清楚
- 第二個演算法意義不大
- 視訊要照到鍵盤
---
## 155. [Min Stack](https://leetcode.com/problems/min-stack/description/) : Medium
### Simulated Interview Process
🕵️♂️: Hello, I am Johnson, the interviewer from Watermelon Company.
🕵️♂️: I'll send you the Google Doc link, and you can start once you're ready.
🕵️♂️: A stack is essential for managing function calls, expression evaluation, implementing undo features, and efficiently handling depth-first search (DFS) in graph or path exploration.
🕵️♂️: To design the MinStack, you need to ensure that all operations—push, pop, top, and getMin—run in constant time, $O(1)$. You'll be working within the constraints of integer values between $-2^{31}$ and $2^{31} - 1$, so your implementation should handle those boundaries. Additionally, you can assume that pop, top, and getMin will always be called on non-empty stacks, which simplifies the need for error handling around empty stacks. With up to 30,000 method calls, it's essential that your design remains efficient under heavy use.
🕵️♂️: Do you have any questions about the problem?
🕶️: Yes. So, does a MinStack simply have an extra function to track the minimum value in the stack compared to a regular stack?
🕵️♂️: You can say that again.
🕶️: One more thing, can I use the stack library to implement the code?
🕵️♂️: Yes, you can use the stack library.
🕶️: To make sure I understand the problem correctly, I'd like to explain it with an example.
Input: `["MinStack","push","push","push","getMin","pop","top","getMin"]
[[],[-2],[0],[-3],[],[],[],[]]
`
Output: `[null,null,null,null,-3,null,0,-2]`
🕶️: Am I understanding the problem correctly?
🕵️♂️: Yes, you can start writing your code.
🕶️: Before writing the code, I'd like to explain my approach.
🕶️: My approach is straightforward. The pop, top, and getMin functions work similarly to a regular stack. The key difference is in the MinStack constructor: I use a stack where each element stores both its value and the minimum value of the stack up to that point.
🕶️: This solution is very simple, so the time complexity of each operation is simply $O(1)$.
---
### Recording two value using one stack
```cpp
class MinStack {
public:
stack<pair<int, int>> minstack;
MinStack() {}
void push(int val) {
if(minstack.empty()){
minstack.push({val,val});
}
else{
int peek = minstack.top().second;
minstack.push({val,min(val,peek)});
}
}
void pop() {
minstack.pop();
}
int top() {
return minstack.top().first;
}
int getMin() {
return minstack.top().second;
}
};
/**
* Your MinStack object will be instantiated and called as such:
* MinStack* obj = new MinStack();
* obj->push(val);
* obj->pop();
* int param_3 = obj->top();
* int param_4 = obj->getMin();
*/
```
---
🕵️♂️: Excellent, Mr. Sakuragi! This is quite challenging, but it seems you’re the expert we’ve been searching for. We’ll send a follow-up interview notification to your email. Best of luck!
🕶️: Thank you very much, Mr. Johnson.
---
### Initial Review
Feedback for the interviewer:
- Speech was too choppy.
- Enunciate more clearly.
- Use fewer filler words (uh, then).
- Could discuss the space complexity of the algorithm in more depth.
- Should have questioned the code (since it wasn’t tested).
- The question should be chosen more carefully (avoid selecting one with little room for improvement).
Feedback for the interviewee:
- Speech was too choppy.
- Enunciate more clearly.
- Too many edits during the coding process.
- Use fewer filler words (uh, then).
- There were typos in the code.
- Code explanations need to be clearer.
- Make sure the keyboard is visible on camera.