# 2024 年「[資訊科技產業專案設計](https://hackmd.io/@sysprog/info2024/https%3A%2F%2Fhackmd.io%2F%40sysprog%2FH1-4KbanC)」HW1
> 貢獻者: 菜就多練-Rookie
## [121. Best Time to Buy and Sell Stock](https://leetcode.com/problems/best-time-to-buy-and-sell-stock/description/)
> [面試影片(漢)](https://youtu.be/7r467Q1RbvU)
### 題目描述
給定一檔股票,其每日的價格變動資訊儲存在`prices`陣列中,其中`prices[i]`表示第`i`天的股票價格,我們可以選擇在某日買入股票,並於而後的某日賣出股票,請回傳這次交易所能獲得的最大收益,若無法獲得收益,則回傳`0`。
### 程式碼解說
#### *解法一:窮舉法*
直接使用兩層for迴圈,窮舉股票<買入,賣出>的所有可能,若找到更大的獲利且符合先買再賣順序,則更新`max_profit`,最後回傳`max_profit`即可。
##### 時間複雜度:$O(N^2)$
```c++=
class Solution {
public:
int maxProfit(vector<int>& prices) {
if (prices.empty()) return 0;
int max_profit = 0;
for (int i=0; i<prices.size(); i++){
for (int j=0; j<prices.size(); j++){
if(prices[j] - prices[i] > max_profit && j>i) max_profit = prices[j] - prices[i];
}
}
return max_profit;
}
};
```
#### *解法二:貪婪法*
首先紀錄股票的買價`buy_price`(初始化為第一天股票的價格`prices[0]`)。接著用一個for迴圈去掃每天股價,每當遇到更低的股票價格時,將`buy_price`更新為該價格。此外,每天都用當天的價格扣掉目前股票的買價來計算潛在的收益`potential_profit`,若潛在收益大於我們儲存的最大收益`max_profit`,則更新`max_profit`。最後同樣回傳`max_profit`即可。
##### 時間複雜度:$O(N)$
```c++=
class Solution {
public:
int maxProfit(vector<int>& prices) {
if (prices.empty()) return 0;
int max_profit = 0, buy_price = prices[0];
for (int i=0; i<prices.size(); i++){
if (buy_price > prices[i]) buy_price = prices[i];
int potential_profit = prices[i] - buy_price;
if (potential_profit > max_profit) max_profit = potential_profit;
}
return max_profit;
}
};
```
### 面試過程檢討
#### 整體
- 太常講口頭禪了,面試過程中不斷地說"好"、"然後"、"這樣"、"那" 等等字眼,讓人聽的有點煩躁。
#### Interviewer
- [00:25](https://www.youtube.com/watch?v=7r467Q1RbvU#t=0m25s): 題目該解釋清楚,而不是說詳細的內容你自己看,這樣挺不負責任的。
- [08:30](https://www.youtube.com/watch?v=7r467Q1RbvU#t=8m30s): 口吃了,身為面試官不用緊張吧,口吃是怎樣。
- [13:52](https://www.youtube.com/watch?v=7r467Q1RbvU#t=13m52s): 一直抖是怎樣,好好坐端正說話可以嗎,這是視訊面試餒。
- [14:18](https://www.youtube.com/watch?v=7r467Q1RbvU#t=14m18s): 面試者用了一個貪婪演算法,但是沒有講出"貪婪"這個關鍵字,面試官應該可以在面試者做完後和面試者聊一下這部分。
- [14:21](https://www.youtube.com/watch?v=7r467Q1RbvU#t=14m21s): 這一題還有DP的做法,面試官應該再次詢問面試者有沒有其他想法,而非直接結束。
#### Interviewee
- [04:19](https://www.youtube.com/watch?v=7r467Q1RbvU#t=4m19s): 話講太急了,感覺很想表達但表達不清,導致發出一堆沒必要的聲音,應該要先想好怎麼講再開始說。
- [04:43](https://www.youtube.com/watch?v=7r467Q1RbvU#t=4m43s): 口誤,不是`prices[a]`是`prices[j]`,a跟j的發音念清楚。
- [04:48](https://www.youtube.com/watch?v=7r467Q1RbvU#t=4m48s): code打到一半就開始test了,但依照REACTO,應該把code打完再開始test。
- [06:43](https://www.youtube.com/watch?v=7r467Q1RbvU#t=6m43s): `i=j`又是什麼,同樣講太急,請組織好語言再講。
- [10:54](https://www.youtube.com/watch?v=7r467Q1RbvU#t=10m54s): 這裡應該補充說我們股票買入的價格`buy_price`會時時更新成目前出現過最低的價格,論述會比較完整。
## [400. Nth Digit](https://leetcode.com/problems/nth-digit/description/)
> [面試影片(漢)](https://www.youtube.com/watch?v=3NaAHZwjTVI)
### 題目描述
給一個數字`n`,在從`1`開始的升冪序列中(`[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ...]`),請回傳第`n`個digit。
### 程式碼解說
#### *解法一:暴力法*
直接把`[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ...]`這個數字序列建出來`sequence`,接著題目querry第`n`個,就回傳`sequence[n-1]`即可。
要回傳`n-1`項是因為`sequence`是從`1`開始,但index是從`0`開始,所以`sequence`的第`n-1`項才是答案。
##### 時間複雜度:$O(N)$
```c++=
class Solution {
public:
int findNthDigit(int n) {
string seqence;
for(int i=1;i<=n;i++){
seqence.append(to_string(i));
}
return (seqence[n-1] - '0');
}
};
```
#### *解法二:分析digit規率*
透過觀察得知:
個位數從1\~9(共9個數,每個數佔1 digit),
十位數從10\~99(共90個數,每個數佔2 digit),
百位數從100\~999(共900個數,每個數佔3 digit)...以此類推。
因此,我們可以用該位數的個數\*digit來得出該位數在序列中總共佔幾個digit。像是個位數總共佔了9\*1個digit,十位數總共佔了90\*2個digit,百位數總共佔了900\*3個digit...等等。
所以,若`n`大於`9*1`,就代表`n`超過了個位數的範疇(至少是十位數),若`n`**又**大於`90*2`就代表`n`超過了十位數的範疇(至少是百位數)...一直到n沒有大於該位數的總數\*digit時,即代表n在的那個數屬於這個位數。
因此,我們用`digit`來記錄現在是幾位數(例:個位數digit=1,十位數digit=2),用`count`來記錄這個位數的數總共有幾個(例:個位數count=9,十位數count=90),再用`start`來記錄該位數下要從哪裡開始算(例如個位數從1開始算,十位數從10開始算)。
接著用一個while迴圈,依次判斷n有沒有超過個位數、十位數、百位數...的範疇,若超過,則將n減掉該位數所佔的總digit數,再更新`digit`、`count`、`start`成下一位數的資訊。如此一來,就能得知`n`是幾位數。
接著,我們要找`n`是該位數中的哪個數。由於我們得知現在的digit數,因此我們用`n`除以`digit`,再加上`start`,即可得知n屬於哪個數。
接著要找`n`是該數中的哪個數(例如是12中的1 or 2),因此我們先將該數轉換成字串`num`,接著用`n`對`digit`取餘數即可得知他在`num`中的位置,因此`num[n%digit]`即為序列中第`n`位digit的數字(字元型態),將其轉回int再回傳即可。
**但如同前面所說,由於序列由1開始,index由0開始,因此我們要用`n-1`去操作才會是對的。**
例如`n=14`,經過前面的while後,各變數情況為`n=5, digit=2, count=90, start=10`,接著用`start`加上`(n-1)/digit`後,得到`start=12`,代表此sequence中第14個digit屬於12這個數,接著先將其轉換成字串`"12"`,又因為`(n-1)%digit = 0`,代表它是在`"12"`這個字串的第0個位置`"1"`,將其轉回int即為答案。
##### 時間複雜度:$O(log_{10}N)$
```c++=
class Solution {
public:
int findNthDigit(int n) {
long long count = 9, digit = 1, start = 1;
// 看n是幾位數
while (n > count * digit) {
n -= count * digit;
digit++;
count *= 10;
start *= 10;
}
// 看n屬於哪個數字
start += (n - 1) / digit;
// 看n在該數字的哪個位置
string num = to_string(start);
return num[(n - 1) % digit] - '0';
}
};
```
### 面試過程檢討
#### 整體
- 和前面一樣,口頭禪有點多,待改進。
#### Interviewer
- [03:50](https://www.youtube.com/watch?v=3NaAHZwjTVI#t=3m50s): 有些code很明確真的可行,建議用肯定一點的語氣,不要說聽起來是可行的這種很不確定的回答。
- [03:52](https://www.youtube.com/watch?v=3NaAHZwjTVI#t=3m52s): 面試者使用了一個需要開額外空間的方法,但面試官卻只問了時間複雜度,理應也要問空間複雜度才合理。
- [06:04](https://www.youtube.com/watch?v=3NaAHZwjTVI#t=6m04s): 面試者只說可以用這樣的思路去思考,並沒有提出具體的方法,但面試官直接說是個不錯的方法,不夠嚴謹。
- [06:06](https://www.youtube.com/watch?v=3NaAHZwjTVI#t=6m06s): 在面試者只有思路的情況下,面試官下一步該問有沒有想到策略去執行該思路,而不該直接叫他去寫code。
- [11:45](https://www.youtube.com/watch?v=3NaAHZwjTVI#t=11m45s): 面試者提出要把`n`減1,但這裡其實沒有那麼直觀,感覺面試官這裡可以提出一些問題來問面試者是怎麼知道,或者怎麼觀察到的,去避免面試者可能不求甚解,只知道這樣做會對,而不知道為什麼會對。
- [14:16](https://www.youtube.com/watch?v=3NaAHZwjTVI#t=14m16s): 面試官不要口吃,你的你的什麼啦。
#### Interviewee
- [01:35](https://www.youtube.com/watch?v=3NaAHZwjTVI#t=1m35s): 請組織語言,想好再講。
- [03:21](https://www.youtube.com/watch?v=3NaAHZwjTVI#t=3m21s): 它會回傳到4還是會爆掉,想好再講。
- [05:05](https://www.youtube.com/watch?v=3NaAHZwjTVI#t=5m05s): digit 2的數字有90個,因為10-99,這聽起來很讓人覺得你背答案,理應是要先講10-99是digit 2的數字,才算出總共有90個,而非直接講有90個再補充說是10-99。
- [05:33](https://www.youtube.com/watch?v=3NaAHZwjTVI#t=5m33s): digit 1明明只有9個而非90,請組織好語言再講,一直口誤很扣分。
- [05:52](https://www.youtube.com/watch?v=3NaAHZwjTVI#t=5m52s): 同樣,請把邏輯先打理好再講,不然一直口誤再修正感覺很差。
- [06:38](https://www.youtube.com/watch?v=3NaAHZwjTVI#t=6m38s): 先理清思緒再打code,把變數寫出來但卻講不出代表什麼,只會讓別人覺得你背答案。
- [06:50](https://www.youtube.com/watch?v=3NaAHZwjTVI#t=6m50s): 不該先打while迴圈才講原因,應該先把原因講清楚再開始打code。
- [08:12](https://www.youtube.com/watch?v=3NaAHZwjTVI#t=8m12s): 同樣,邏輯想好再打code,不要打了要解釋又卡很久才想到怎麼解釋。
- [09:26](https://www.youtube.com/watch?v=3NaAHZwjTVI#t=9m26s): 應該在講while迴圈時就講這是在判斷位數,而非舉完小例子後才補上。
- [09:48](https://www.youtube.com/watch?v=3NaAHZwjTVI#t=9m48s): 面試者把code打上去後發現還沒解釋,又把code刪掉,這個做法又更容易讓面試官認為你在背code。
- [13:18](https://www.youtube.com/watch?v=3NaAHZwjTVI#t=13m18s): 該先解釋會什麼它會是答案,而非直接說我們return這個,因為它是答案。
## [83. Remove Duplicates from Sorted List](https://leetcode.com/problems/remove-duplicates-from-sorted-list/description/)
> [面試影片(英)](https://www.youtube.com/watch?v=WpSe3hZwCwM)
### 題目描述
給定一個排序好的linked list的head,請把該linked list中val重複的node刪除,讓所有node的val皆只出現過一次,最後回傳處理完的linked list的head。
### 程式碼解說
#### *解法一:使用set*
由於要刪除重複的val,因此我先想到開一個set`myset`,將linked list中各node的val放進`myset` 裡,每當拜訪node時,就用`myset`判斷該node的val是否已經存在,若不存在,就代表該Node要保留,此時就先將該node的val紀錄進`myset`,並繼續前進下一個node;但若該node的val已經存在於`myset`,代表這個node該被刪掉,此時我們就將我們原本的`next`指標改成再下一個node即可。拜訪完整條linked list後,再回傳head即可。
##### 時間複雜度:$O(NlogN)$
```c++=
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
ListNode *now = head;
set<int> myset;
// 看head是否存在
if (now != NULL) myset.insert(now->val);
else return head;
while(now->next != NULL){
// 沒看過的val,保留node
if( myset.find(now->next->val) == myset.end() ){
myset.insert(now->next->val);
now = now->next;
}
// 有看過的val,刪除node
else{
ListNode *del = now->next;
now->next = now->next->next;
delete(del);
}
}
return head;
}
};
```
#### *解法二:直接比較*
由於題目給的linked list是排序過的,因此其實不用使用set來判斷該val是否出現過,反而可以直接拿現在的node和下一個node來比較他們的val,若不重複則都保留,若有重複則刪除後面的node,同樣拜訪完整條linked list,最後回傳head即可。
##### 時間複雜度:$O(N)$
```c++=
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
ListNode *now = head;
while(now!=NULL && now->next!=NULL){
// 若後面node的val和前面node的val相同,則刪除後面的node
if(now->val == now->next->val) {
ListNode *del = now->next;
now->next = now->next->next;
delete(del);
}
else now = now->next;
}
return head;
}
};
```
### 面試過程檢討
#### 整體
- 英文口說要練,有些地方文法講錯。
- 一樣有些英文的口頭禪,請不要過度的講,聽了有些煩。
#### Interviewer
- [02:17](https://www.youtube.com/watch?v=WpSe3hZwCwM#t=2m17s): 面試者提出一個可行的方法,那個嘆息聲也太大聲,不應該如此否定面試者。
- [02:20](https://www.youtube.com/watch?v=WpSe3hZwCwM#t=2m20s): 什麼是maybe is can work,請組織一下語言再講。
- [11:10](https://www.youtube.com/watch?v=WpSe3hZwCwM#t=11m10s): 使用者將node從linked list上移除後,並沒有`delete`它(還是有占用memory),這裡面試官應該問一下使用者是不是還有什麼是忘記做之類的。
- [11:18](https://www.youtube.com/watch?v=WpSe3hZwCwM#t=11m18s): 不要講的卡卡的,想過再講。
- [12:32](https://www.youtube.com/watch?v=WpSe3hZwCwM#t=12m32s): 面試官問面試者有沒有其他想法,但下一秒直接說give you a hint,應該要讓使用者想,若使用者想不到,再給hint。
- [18:08](https://www.youtube.com/watch?v=WpSe3hZwCwM#t=18m08s): 面試者只有改pointer,沒有將node給真正刪掉,這會造成memory leak,面試官該和面試者聊聊這塊。
#### Interviewee
- [00:15](https://www.youtube.com/watch?v=WpSe3hZwCwM#t=0m15s): 這裡該和面試官打聲招呼。
- [01:50](https://www.youtube.com/watch?v=WpSe3hZwCwM#t=1m50s): 想好再講。
- [03:00](https://www.youtube.com/watch?v=WpSe3hZwCwM#t=3m00s): struct結構尾巴沒有";",這樣會compile error,請熟悉語言。
- [04:18](https://www.youtube.com/watch?v=WpSe3hZwCwM#t=4m18s): temporal不是變數,變數叫做variable,請記熟。
- [05:00](https://www.youtube.com/watch?v=WpSe3hZwCwM#t=5m00s): 想好再講。
- [07:20](https://www.youtube.com/watch?v=WpSe3hZwCwM#t=7m20s): 這裡講的有點饒口,請先整理好思緒。
- [08:05](https://www.youtube.com/watch?v=WpSe3hZwCwM#t=8m05s): we will go this else是啥,應該說we will go this branch,然後鼠標停else那邊。
- [08:20](https://www.youtube.com/watch?v=WpSe3hZwCwM#t=8m20s): 這裡並沒有真的把這個node給刪掉,只是把線給拆掉罷了,講的不嚴謹,應該要在寫code時把它`delete`掉。
- [08:27](https://www.youtube.com/watch?v=WpSe3hZwCwM#t=8m27s): 想好再講。
- [08:58](https://www.youtube.com/watch?v=WpSe3hZwCwM#t=8m58s): 在上一個例子中就會碰到while condition的問題了,但現在才發現,會讓人覺得你剛剛test程式沒有很認真照著程式邏輯走。
- [12:50](https://www.youtube.com/watch?v=WpSe3hZwCwM#t=12m50s): 請組織語言,練練文法。
- [13:07](https://www.youtube.com/watch?v=WpSe3hZwCwM#t=13m07s): 不是比較previous跟next,是比較previous跟current(這裡我突然忘記current這個單字才講next),請記熟,不要講錯。
- [16:45](https://www.youtube.com/watch?v=WpSe3hZwCwM#t=16m45s): 請講this branch。
- [17:43](https://www.youtube.com/watch?v=WpSe3hZwCwM#t=17m43s): 什麼they are not correct,they are not the same才對,不要口誤。