# 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才對,不要口誤。