# 2023 年「[資訊科技產業專案設計](https://hackmd.io/@sysprog/info2023)」作業 1
> 貢獻者: 霍夫曼-Mark Hoffman
> [英文模擬面試錄影-1](https://youtu.be/lyyr50FvWrc)
> [英文模擬面試錄影-1](https://youtu.be/xcuH1O02vsg)
> [英文模擬面試錄影-3](https://youtu.be/ubK1KXEYegE)
> 🧑🦳面試官
> 👦面試者 - Mark Hoffman (霍夫曼)
## 重點摘要:
### [LeetCode 268 - Missing Number](https://leetcode.com/problems/missing-number/)
🧑🦳: 給你一個長度為 n 的整數陣列,其中應該包含 [0, n] 所有整數,請你找出陣列中遺漏的數字。
👦: 我的作法是建立一個長度為 n+1 的整數陣列,在原始陣列的迭代過程中紀錄每一個數字的出現次數,最後再從新建的陣列中找出 0 並回傳索引值。
👦: ***程式實作***。
```cpp
class Solution {
public:
int missingNumber(vector <int> & nums) {
int n = nums.size();
vector <int> occurrences(n + 1, 0);
int i = 0;
for (; i < n; ++i) {
occurrences[nums[i]] += 1;
}
for (i = 0; i <= n; ++i) {
if (occurrences[i] == 0) {
return i;
}
}
return -1;
}
};
```
🧑🦳: However, this approach might have issues with overflow when dealing with very large ranges of numbers. Can you improve your code to handle all cases?
👦: 我可以把陣列的資料型態從「32位元的整數」改成「8位元的布林值」,這樣應該能節省一些空間。***程式實作***。
```cpp
class Solution {
public:
int missingNumber(vector <int> & nums) {
int n = nums.size();
vector <bool> occurrences(n + 1, false);
int i = 0;
for (; i < n; ++i) {
occurrences[nums[i]] = true;
}
for (i = 0; i <= n; ++i) {
if (!occurrences[i]) {
return i;
}
}
return -1;
}
};
```
🧑🦳: 你這樣確實能節省記憶體的使用量,但是效果有限,請你分析一下你的時間複雜度和空間複雜度。
👦: 由於程式共進行了兩次 O(n) 的迭代,故時間複雜度為 O(2n) = O(n),而空間複雜度則是 O(n),因為我們建立了一個長度為 n+1 的陣列。
🧑🦳: 分析得不錯!你說你的空間複雜度是 O(n)?我希望你把它降到 O(1)。
👦: 我想到一個更好的解法,因為每一個數字只會出現一次,他們的總和會是 1+2+3+...+n = (n+1)×n÷2,與其記錄出現次數,我們可以將所有數字加總起來,最後再與公式解相減,它們的差就會是漏掉的數字。
👦: ***程式實作***。
```cpp
class Solution {
public:
int missingNumber(vector <int> & nums) {
int n = nums.size();
int expect = (n + 1) * n / 2;
int actual = 0;
for (int i = 0; i < n; ++i) {
actual += nums[i];
}
int difference = expect - actual;
return difference;
}
};
```
### [LeetCode 387 - First Unique Character in a String](https://leetcode.com/problems/first-unique-character-in-a-string/)
🧑🦳: 給你一個只有小寫字母的英文字串,請你找出第一個只出現一次的字元。
👦: 我的作法是透過第一次的迭代記錄每一個字元的出現次數,並在第二次的迭代檢查其出現次數,如果次數為 1 就回傳索引值,如果最後沒有符合條件的字元就回傳 -1。
👦: ***程式實作***。
```cpp
class Solution {
public:
int firstUniqChar(string s) {
map <char, int> occurrences;
int n = s.length();
int i = 0;
for (; i < n; ++i) {
if (occurrences.find(s[i]) == occurrences.end()) {
occurrences[s[i]] = 1;
continue;
}
occurrences[s[i]] += 1;
}
for (i = 0; i < n; ++i) {
if (occurrences[s[i]] == 1) {
return i;
}
}
return -1;
}
};
```
🧑🦳: 請你分析一下你的程式的時間複雜度與空間複雜度。
👦: 程式碼有用到 map 這個結構,存取裡面的值為 O(log n),但因為總共只有 26 個英文字母作為 key,因此其複雜度可視作 O(1),而呼叫次數又為 n 次,故時間複雜度為 O(n)。空間複雜度則為 O(1),原因同上(只有 26 個英文字母)。
🧑🦳: 你能不能讓你的程式跑得更快一點?
👦: 我可以把 map 換成一般的 C 陣列,這樣存取資料只要一個指令,而不是進行二分搜尋(Binary Search)。
👦: ***程式實作***。
```cpp
class Solution {
public:
int firstUniqChar(string s) {
constexpr int p = 123;
int n = s.length();
int occurrences[p];
int i = 97;
for (; i < p; ++i) {
occurrences[i] = 0;
}
for (i = 0; i < n; ++i) {
occurrences[s[i]] += 1;
}
for (i = 0; i < n; ++i) {
if (occurrences[s[i]] == 1) {
return i;
}
}
return -1;
}
};
```
### [LeetCode 16 - 3-Sum Closest](https://leetcode.com/problems/3sum-closest/)
🧑🦳: 給你一個整數陣列 nums 和一個整數 target,請你找出最接近 target 的三個數的和。
👦: 我的作法是用三層巢狀迴圈,找出陣列中所有可能三個數的和,並記錄最接近 target 的答案。
👦: ***程式實作***。
```cpp
class Solution {
public:
int threeSumClosest(vector <int> & nums, int target) {
int i, j, k, n = nums.size(), n_1 = n - 1, n_2 = n - 2;
int tmpSum, closestSum = INT_MAX / 2;
for (i = 0; i < n_2; ++i) {
for (j = i + 1; j < n_1; ++j) {
for (k = j + 1; k < n; ++k) {
tmpSum = nums[i] + nums[j] + nums[k];
if (std::abs(tmpSum - target) < std::abs(closestSum - target)) {
closestSum = tmpSum;
}
}
}
}
return closestSum;
}
};
```
🧑🦳: 我想請你分析你的程式碼的複雜度。
👦: 因為我用了三層的巢狀迴圈,而每一層都會進行 O(n) 的迭代,因此我的程式碼的時間複雜度是 O(n^3)。空間複雜度則是 O(1)。
🧑🦳: 你的時間複雜度是 O(n^3)?
👦: 是的,不過我想到了一個降低複雜度的方法。與其用三層的巢狀迴圈,我們可以先將陣列進行排序,再將最內層的迴圈改為二分搜尋。***程式實作***。
```cpp
class Solution {
public:
int threeSumClosest(vector <int> & nums, int target) {
int i, j, k, n = nums.size(), n_1 = n - 1, n_2 = n - 2;
int tmpSum, closestSum = INT_MAX / 2;
std::sort(nums.begin(), nums.end());
for (i = 0; i < n_2; ++i) {
for (j = i + 1; j < n_1; ++j) {
auto sectBegin = nums.begin() + j + 1;
int remain = target - nums[i] - nums[j];
auto found = std::lower_bound(sectBegin, nums.end(), remain);
if (found < nums.end()) {
tmpSum = *found;
if (found > sectBegin) {
if (std::abs(*(found - 1) - remain) < std::abs(tmpSum - remain)) {
tmpSum = *(found - 1);
}
}
}
else {
tmpSum = *(found - 1);
if (found < (nums.end() - 1)) {
if (std::abs(*(found + 1) - remain) < std::abs(tmpSum - remain)) {
tmpSum = *(found + 1);
}
}
}
tmpSum = tmpSum + nums[i] + nums[j];
if (std::abs(tmpSum - target) < std::abs(closestSum - target)) {
closestSum = tmpSum;
}
}
}
return closestSum;
}
};
```
🧑🦳: 不錯,那麼你的時間複雜度現在是多少?
👦: 兩層的巢狀迴圈加上一個二分搜尋的複雜度是 O(n^2 log n)。
🧑🦳: 你說你的時間複雜度是 O(n^2 log n)?你不覺得它還是有點慢嗎?你能讓它再快一點嗎?
👦: 你要我讓它再快一點?因為陣列已經排序過了,因此隨機三個數,靠左的數較小,而靠右的數較大。如果三數的和過大,就把最小值(左)換成更小的值;如果三數和過小,就把最大值(右)換成更大的值。這樣時間複雜度就會是 O(n log n)。***程式實作***。
```cpp
class Solution {
public:
int threeSumClosest(vector <int> & nums, int target) {
int i, j, k, n = nums.size(), n_1 = n - 1, n_2 = n - 2;
int tmpSum, closestSum = INT_MAX / 2;
std::sort(nums.begin(), nums.end());
for (i = 1; i < n_1; ++i) {
j = i - 1;
k = i + 1;
while ((j >= 0) && (k < n)) {
tmpSum = nums[i] + nums[j] + nums[k];
if (std::abs(tmpSum - target) < std::abs(closestSum - target)) {
closestSum = tmpSum;
}
if (tmpSum < target) {
k += 1;
continue;
}
if (tmpSum > target) {
j -= 1;
continue;
}
break;
}
}
return closestSum;
}
};
```
## 初步檢討:
1. REACTO 之 E 部分,應該更全面列舉例子,尤其是第三場面試。
2. 受試者及面試官說話之節奏過於單調,缺乏熱忱。
3. 受試者應盡可能一開始就提出最佳解,以免浪費時間。
4. 受試者與面試官應多互動,尤其是受試者,應該取得面試官的回饋。
5. 若遇到意外(第三場面試),受試者應立即修正錯誤,而非等面試官指點。
---
## 第二次作業-他評01
### 關於interviewer
* [ ] 可改進之處
* [4:51](https://youtu.be/ubK1KXEYegE?t=291): 就如同你上面說的,若是受試者在test階段發現錯誤時要請受試者修改程式碼。
### interviewee
- [ ] 優點
* 有在撰寫程式碼時適當加入註解讓 code 更明瞭
* REACTO各階段講的都蠻詳細的
- [ ] 可改進之處
* 因為是在leetcode上直接做編譯跟測資,但是我會比較想要受試者自己用測資帶入程式內部去做測試。
* 建議不要使用leetcode當作coding工具,受試者可能會看到leetcode題目當中關鍵字就容易想出解題方針。
## 第二次作業-他評02
### 關於interviewer
* 面試者提出$O(n^2)$ 可以適時的提點面試者,因為本來就會預期大概在submit階段過不了
* 提示不會太多也很剛好,很符合實際卡關時會面對的情況
### 關於interviewee
* 對暴力解的優化展現面試者對C++語言上操作的靈活性跟熟悉度。
* 整體上對於暴力解跟優化解法都給出很清楚的解釋