--- tags: info2022-homework1 --- # 2022 年「[資訊科技產業專案設計](https://hackmd.io/@sysprog/info2022)」作業 1 _貢獻者 : 秘客 Cypto_ > [模擬面試錄影(漢)](https://youtu.be/g2HdYJwS0oc) > [模擬面試錄影(漢)](https://youtu.be/-5HsZK25XIs) > [模擬面試錄影(英)](https://youtu.be/pt9NQrQoTyE) :face_with_monocle: : Interviewer :nerd_face: : Interviewee ## 9. Palindrome Number ### 測驗說明問與答 :face_with_monocle: : 那我想請你實作一個function,輸入是一個整數,回傳此整數是否符==回文==的規定。 :nerd_face: : 好的,所以我需要製作一個function,檢查輸入進來的整數是否頭尾對應的數字相同,如果對應數字都相同就代表是回文數值對嗎? :face_with_monocle: : 是的。 :nerd_face: : 好,那舉例來說 : 121、2442 ,這些都是符合條件的數字,356,就是不符合回文的。 :nerd_face: : 那就我現在的觀察來說,因為要前後進行比對,所以我先把數字轉變成字串的型態,然後用兩個指標,一個從前面一個從後面,利用迴圈開始進行比對。 :nerd_face: :那我想請問一下題目是否有規定輸入述職的大小範圍。 :face_with_monocle: : 有的,介於 -2^32^ 跟 2^32^ 之間。 :nerd_face: : 好那我就開始進行實作。 ```cpp bool IsPalindrome(int x) { char alter[50]; sprintf(alter,"%d",x); int front = 0 ; int tail = strlen(alter)-1; while(front<tail) { if (alter[front]!=alter[tail]) return false ; front ++ ; tail -- ; } return x ; } ``` :face_with_monocle: : 那你是否有辦法不轉換成字串來進行判斷? :nerd_face: : 好的,那我這邊的想法是先做出一個反轉過來的數字,再跟原本的數字去進行比對。 ```cpp bool IsPalindrome(int x) { int tmp=0; if(x<0||(x%10 == 0 && x!=0)) return false; while(x>tmp) { tmp = tmp * 10 + x%10 ; x = x/10; } if(x != tmp && (tmp/10 != x)) { return false; } else return x; } ``` :nerd_face: : 這邊就是我利用數值運算所做出的function,想請問對於我的方法有沒有什麼問題。 :face_with_monocle: : 沒有,第一題的面試就到這邊結束。 ### 同儕檢討 - [00:03](https://youtu.be/g2HdYJwS0oc?t=3) 不要講第幾第幾道題目 - [00:26](https://youtu.be/g2HdYJwS0oc?t=26) 對稱的數字聽起來不是很直覺(以不知道回文的定義來說)到底是從中間切開用鏡子來看一樣(eg.151不是回文,101是回文),還是真正回文的定義(151 和 101 都是回文)。 - [00:36](https://youtu.be/g2HdYJwS0oc?t=36) 這邊舉例就可以把上面那一點簡單的的說明一下。 - [01:33](https://youtu.be/g2HdYJwS0oc?t=93) char 宣告大小的取捨。 - [05:39](https://youtu.be/g2HdYJwS0oc?t=339) ```retrun x;``` 這裡沒有考慮到 0 這個整數的問題,會回傳 false 而不是 true 。 - [11:25](https://youtu.be/g2HdYJwS0oc?t=685) 跟上個一樣 ```return x;``` 沒有考慮到 0 的狀況。 - [12:29](https://youtu.be/g2HdYJwS0oc?t=749) 把```if(x<0||(x%10 == 0 && x!=0)) return false;``` 放到 ```int tmp = 0;``` 前面會比較好一點。 - [13:34](https://youtu.be/g2HdYJwS0oc?t=814) 最後面的 ```if``` 可以用以下寫法會比較簡潔。 ```cpp bool IsPalindrome(int x) { if(x<0||(x%10 == 0 && x!=0)) return false; int tmp=0; while(x > tmp) { tmp = tmp * 10 + x%10 ; x = x/10; } return x == tmp || x == tmp/10; } ``` - [13:36](https://youtu.be/g2HdYJwS0oc?t=816) 是不是可以比較一下時間跟空間複雜度之於這兩種不同方法。 針對 interviwer 的檢討: * 應該要稍微講解回文定義,interviweee才能夠更好理解題目。 * 應該要主動講出給定數值的範圍,這樣對於interviwer會比較好構思。 * 針對 follow-ups,可改為給定數值的二進位型態 (binary representation) 是否為 palindrome,這樣問題不僅更貼合現實應用場景,還能看出 interviewee 的程式設計基本功 (如 bitwise 運算) 針對 interviwee 的檢討: * 在選擇利用字串進行處理時要考慮到空間複雜度的問題,如果字串設太大會有空間浪費 * 如果限制不能使用乘法和除法運算,那 bitwise 運算就是關鍵,可見以下實作: ```c bool isPalindrome(int x) { if (x < 0) return false; if (x == 0) return true; int n = x; unsigned rev = 0; do { unsigned q = (x >> 1) + (x >> 2); q += q >> 4; q += q >> 8; q += q >> 16; unsigned qq = q; q >>= 3; unsigned char rem = x - ((q << 1) + (qq & ~7UL)); if (rem > 9) q++, rem -= 10; rev = ((rev << 1) + (rev << 3)) + rem; x = q; } while (x); return n == rev; } ``` 上方程式碼之改進如下: ```diff= bool isPalindrome(int x) { if (x < 0) return false; if (x == 0) return true; int n = x; unsigned rev = 0; do { unsigned q = (x >> 1) + (x >> 2); q += q >> 4; q += q >> 8; q += q >> 16; - unsigned qq = q; q >>= 3; - unsigned char rem = x - ((q << 1) + (qq & ~7UL)); + unsigned char rem = x - ((q << 1) + (q << 3)); if (rem > 9) q++, rem -= 10; rev = ((rev << 1) + (rev << 3)) + rem; x = q; } while (x); return n == rev; } ``` * 第 13 行: q 得到的結果逼近 0.79999999981 倍的 x,約為 0.8x * 第 15 行: q 除以 8 得到逼近 0.1x (x/10) * 第 16 行: **`qq & ~7UL` 為將最右邊三個 bits 設為 0,相當於 `q << 3`** * 第 17 行: x - (0.2x + 0.8x),x - (x/10)*10 取得最後一個位數 * 第 18 行: 因為此計算方式是模擬除以 10,當 rem > 9 時,代表有誤差存在,q (商) 比預期少 1,故 q += 1、rem -= 10 * 以測資 INT_MAX (2147483647) 進行測試會發現 q 的誤差剛好是 1 * 在第 15 行新增以下程式碼印出 q 值 ```c printf("理想值: %d\n", x / 10); (x / 10 != q) ? printf("計算值: %d<---不一樣\n\n", q) : printf("計算值: %d\n\n", q); ``` :::spoiler (點擊展開) 得到以下結果: ``` 理想值: 214748364 計算值: 214748364 理想值: 21474836 計算值: 21474836 理想值: 2147483 計算值: 2147483 理想值: 214748 計算值: 214748 理想值: 21474 計算值: 21474 理想值: 2147 計算值: 2147 理想值: 214 計算值: 214 理想值: 21 計算值: 21 理想值: 2 計算值: 1<---不一樣 理想值: 0 計算值: 0 ``` ::: * 第 20 行: rev = (2 \* rev + 8 \* rev ) + rem = 10 \* rev + rem --- ## 24. Swap Nodes in Pairs ### 測驗說明問與答 :face_with_monocle: : 第二個問題,給定一個linked list,請你寫出一個function將linked list裡面的每兩個node進行互換,且不能夠直接改變node裡面的value。 :nerd_face: : 好的,所以我需要把這個linked list裡面的node兩個為一組進行交換,且不能夠透過直接改值來完成。 :nerd_face: : 舉例來說[1,2,3,4]經過function交換過後應該會變為,[2,1,4,3]。 :nerd_face: : 我的想法是既然不能夠改值,那就是直接對linked list連接的位址去進行變動,利用迴圈兩兩一組進行改動下去。 ```cpp struct ListNode* swapPairs(struct ListNode* head){ struct ListNode *realhead,*current,*previous,*store ; if(head->next != NULL) realhead = head->next ; else return head; current = head ; previous = NULL; while(current != NULL && current->next != NULL) { store = current->next->next ; current->next->next = current ; if(previous!=NULL) previous->next = current->next ; current->next = store ; previous = current ; current = store ; } return realhead ; } ``` ### 同儕檢討 - [01:00](https://youtu.be/-5HsZK25XIs?t=60) 我覺得這題的題目沒有敘述得很清楚,到底是 (1,2) 互換 (3,4) 互換還是 (1,2) 先換 (2,3) 再換...以此類推。所以我會問得更詳細一點。 - [03:51](https://youtu.be/-5HsZK25XIs?t=231) NULL 發音問題,而且```if(head->next != NULL)``` 這個部分,如果輸入值為```[]```的時候,會有存取空指標的錯誤產生。 針對 interviwer 的檢討: * 不能直接改node值,應該要明說不能直接改node裡的value值會比較好。 針對 interviwee 的檢討: * 在講程式中間的指標變換的時候可以把current等等標記在範例上,會比只用說的更好去理解。 --- ## 21. Merge Two Sorted Lists ### 測驗說明問與答 :face_with_monocle: : Ok,next problem. You are given the heads of two sorted linked lists . :face_with_monocle: : Please Merge the two linked list into one sorted linked list , and return the head of the merged linked list . :nerd_face: : So i need to merge two linked list and keep the merged linked list be sorted . :nerd_face: : For example , list1 = [1,3,5] 、 list2 = [2,4,6] , result = [1,2,3,4,5,6] . :nerd_face: : My ideal is to compare this two's front node , and put the minimum node into new linked list untill all node is finish . ```cpp struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2){ if(list1 ==NULL) return list2; else if(list2 == NULL) return list1; struct ListNode *result ,*tmp ; if(list1->val < list2->val) { result = list1; list1 = list1->next ; } else { result = list2 ; list2 = list2->next ; } tmp = result; while(list1 != NULL && list2 !=NULL) { if(list1->val > list2->val) { tmp->next = list2; list2 = list2->next ; tmp = tmp->next; tmp->next = NULL; } else { tmp->next = list1 ; list1 = list1->next; tmp = tmp->next ; tmp->next = NULL; } } if(list1 != NULL) { tmp ->next =list1; } else if (list2 != NULL) { tmp->next = list2; } return result; } ``` 針對 interviwer 的檢討: * 應該要提供linked list的資料結構定義,interviwee會比較方便撰寫程式。 針對 interviwee 的檢討: * 過程中可以利用範例講解可以讓程式更加易懂。 * 實作的程式碼過於冗長,請參見〈[你所不知道的 C 語言: linked list 和非連續記憶體](https://hackmd.io/@sysprog/c-linked-list)〉提供的實作: ```c struct ListNode *mergeTwoLists(struct ListNode *L1, struct ListNode *L2) { struct ListNode *head = NULL, **ptr = &head, **node; for (node = NULL; L1 && L2; *node = (*node)->next) { node = (L1->val < L2->val) ? &L1: &L2; *ptr = *node; ptr = &(*ptr)->next; } *ptr = (struct ListNode *)((uintptr_t) L1 | (uintptr_t) L2); return head; } ``` 簡潔又看得出實作技巧