--- tags: 進階電腦系統理論與實作, NCKU Linux Kernel Internals, 作業系統 --- # 2020q3 Homework7 (quiz7) contributed by < `RusselCK` > ###### tags: `RusselCK` > [ bitwise 操作 & 遞迴呼叫 & 編譯器最佳化 練習題](https://hackmd.io/@sysprog/2020-quiz7) ## Q1. 判斷 是否為 [Tail Recursion](https://en.wikipedia.org/wiki/Tail_call) ```cpp= int funcA(int i) { if (i == 0) return 0; return 5 * funcA(i - 1); } int funcB(int i) { if (i == 0) return 0; return funcB(i - 1); } ``` :::info * Tail call 指的是一個函式的最後一條語句也是一個返回呼用函式的語句 * 即這個呼叫的返回值直接被當前函式返回的情形 * 在函式體末尾被返回的可以是對另一個函式的呼叫,也可以是對自身呼叫 * 若這個函式在尾位置呼叫本身(或是一個尾呼叫本身的其他函式等等),則稱這種情況為 Tail Recurtion ::: - 程式碼解析 * `#12`: `funcB(i)` 的最後一句 是 `return funcB(i - 1);` * 呼叫本身,符合 Tail Recurtion 的定義 * 編譯器 可以做 Tail Call Optimization, TCO ```graphviz digraph{ rankdir = LR step1[shape = record label= "funcB(i)"] step2[shape = record label= "funcB(i)|funcB(i-1)"] step3[shape = record label= "funcB(i)|funcB(i-1)|funcB(i-2)"] step1 -> step2 -> step3 } ``` * function call 原本的佔用 stack 情形 ```graphviz digraph{ rankdir = LR step1[shape = record label= "funcB(i)"] step2[shape = record label= "funcB(i-1)"] step3[shape = record label= "funcB(i-2)"] step1 -> step2 -> step3 } ``` * 以 `funcB(i)` 來說,執行到 `return funcB(i - 1)` 後,接下來只要等 `funcB(i - 1)` 的回傳值就可以了 * 也就是說 `funcB(i)` 已經做完了,不需要再留 stack space 給 `funcB(i)` * 藉此達到省 stack space 的效果 - `#5` : `funcA(i)` 的最後一句 為 `return 5 * funcA(i - 1);` - 需要等 `funcA(i - 1)` 回傳 再 乘上 5 ,才是真正的返回值 - 不符合 Tail Recurtion 的定義 - 編譯器無法對其做 TCO ## Q2. ## Q3. LeetCode [226. Invert Binary Tree](https://leetcode.com/problems/invert-binary-tree/) ![](https://i.imgur.com/chni5ov.png) ```cpp= struct TreeNode { int val; struct TreeNode *left; struct TreeNode *right; }; struct TreeNode *invertTree(struct TreeNode *root) { if (!root) return NULL; if (!root->left && !root->right) return root; struct TreeNode *tmp = root->left; root->left = root->right; root->right = tmp; invertTree(root->left); invertTree(root->right); return root; } ``` * 程式碼解析: ```cpp=9 if (!root) return NULL; ``` * 若為 空樹,回傳 NULL ```cpp=12 if (!root->left && !root->right) return root; ``` * 若左右都無子樹,可直接回傳 `root` ```cpp=15 struct TreeNode *tmp = root->left; root->left = root->right; root->right = tmp; ``` * 將 左、右子樹 交換位置 ```cpp=18 invertTree(root->left); invertTree(root->right); ``` * 左、右子樹 分別遞迴作 `invertTree` * 先後順序不影響結果 ## Q4. LeetCoee [201. Bitwise AND of Numbers Range](https://leetcode.com/problems/bitwise-and-of-numbers-range/) * Given a range $[m, n]$ where $0 \leq m \leq n \leq 2147483647$, return the bitwise AND of all numbers in this range, inclusive. [解法 I](https://hackmd.io/@Zero871015/LeetCode-201) : ```cpp= int rangeBitwiseAnd(int m, int n) { if (m == 0) return 0; int diff = n - m; int count = diff ? 32U - __builtin_clz(diff) : 0; return m & n & MASK; // MASK = ~((1U << count) - 1) } ``` | | Decimal | Binary | |:-----:|:-------:|:---------:| | m | 100 | ==011==0 0100 | | n | 120 | ==011==1 1000 | | m & n | | ==011==0 0000 | | n - m | 20 | 0001 0100 | |Mask|`~((1U << 5) - 1)` | 1110 0000| | Decimal | Binary | Decimal | Binary | Decimal | Binary | |:-------:|:---------:|:-------:|:---------:|:-------:|:---------:| | | | 8 | 0000 1000 | 16 | 0001 0000 | | 1 | 0000 0001 | 9 | 0000 1001 | 17 | 0001 0001 | | 2 | 0000 0010 | 10 | 0000 1010 | 18 | 0001 0010 | | 3 | 0000 0011 | 11 | 0000 1011 | 19 | 0001 0011 | | 4 | 0000 0100 | 12 | 0000 1100 | 20 | 0001 0100 | | 5 | 0000 0101 | 13 | 0000 1101 | | | | 6 | 0000 0110 | 14 | 0000 1110 | | | | 7 | 0000 0111 | 15 | 0000 1111 | | | * 題目要求為 $m$ & $(m+1)$ & ... & $(n-1)$ & $n$ * `m & n` 可以得知 結果之較大位數的 bits ( 第一個表格畫螢光的部份 ) * 接著我們還缺少的是,剩餘的 bits 應該為多少? * 以上方例子來看, `m`、`n` 相差 20 * 由第二個表格可知 1 & 2 & ...& 20 = 000==0 0000== * 且能保證 後 5 個 bits 結果 必為 0 * `8 - __builtin_clz(20)` = 8 - 3 = 5 * 因此,我們還須要一個 Mask = 1110 0000 = ~( 0001 1111 ) = ~( (0010 0000) - 1 ) = ~( (1 << 5) - 1 ) * 故 **MASK = `~((1U << count) - 1)`** ## Q5. LeetCode [97. Interleaving String](https://leetcode.com/problems/interleaving-string/) ![](https://i.imgur.com/UJyMQmZ.png) #### [解法 I](https://medium.com/@bill800227/leetcode-97-interleaving-string-18b1202fb0ea) : [Dynamic Programming](https://en.wikipedia.org/wiki/Dynamic_programming) ```cpp= bool isInterleave(char * s1, char * s2, char * s3){ size_t n = strlen(s1), m = strlen(s2); if ((n + m) != strlen(s3)) return false; bool dp[n+1][m+1]; for (int i = 0; i < (n + 1); ++i ) memset(dp[i], false, (m + 1)); dp[0][0] = 1; for(int i = 1; i < (n + 1); ++i) dp[i][0] = dp[i - 1][0] && (s1[i - 1] == s3[i - 1]); for(int j = 1; j < (m + 1); ++j) dp[0][j] = dp[0][j - 1] && (s2[j - 1] == s3[j - 1]); for(int i = 1; i < (n + 1); ++i){ for(int j = 1; j < (m + 1); ++j){ dp[i][j] = (dp[i - 1][j] && (s1[i - 1] == s3[i + j - 1])) || (dp[i][j - 1] && (s2[j - 1] == s3[i + j - 1])); } } return dp[n][m]; } ``` | $s_1$ \ $s_2$ | | d | b | b | c | a | | -------------:|:---:|:---:|:---:|:---:| --- |:---:| | | 1 | 0 | 0 | 0 | 0 | 0 | | a | 1 | 0 | 0 | 0 | 0 | 0 | | a | 1 | 1 | 1 | 1 | 1 | 0 | | b | 0 | 1 | 1 | 0 | 1 | 0 | | c | 0 | 0 | 1 | 1 | 1 | 1 | | c | 0 | 0 | 0 | 0 | 0 | 1 | * `DP[i][j]` : `s1[0:i-1]` 與 `s2[0:j-1]` 是否能組成 `s3[0:i+j-1]` * 程式碼解析: ```cpp=3 size_t n = strlen(s1), m = strlen(s2); if ((n + m) != strlen(s3)) return false; ``` * 先確定 `s1` + `s2` 的長度是否等於 `s3` 的長度 ```cpp= bool dp[n+1][m+1]; for (int i = 0; i < (n + 1); ++i ) memset(dp[i], false, (m + 1)); dp[0][0] = 1; ``` * 建立 DP 表格,並設置 `dp[0][0]` 為 1,其餘為 0 * [效能評估](https://leetcode.com/submissions/detail/414107932/): ![](https://i.imgur.com/w0nAG7c.png) #### 解法 II : DP with 1D Array ```cpp= bool isInterleave(char *s1, char *s2, char *s3) { size_t n = strlen(s1), m = strlen(s2); if ((n + m) != strlen(s3)) return false; bool dp[m + 1]; memset(dp + 1, false, m * sizeof(bool)); dp[0] = true; for (int i = 0; i <= n; i++) { for (int j = 0; j <= m; j++) { if (i == 0 && j == 0) continue; int p = RRR; // RRR = i + j - 1 if (i == 0) dp[j] = dp[j - 1] & (s2[j - 1] == s3[p]); else { dp[j] &= (s1[i - 1] == s3[p]); if (j > 0) dp[j] |= dp[j - 1] & (s2[j - 1] == s3[p]); } } } return dp[m]; } ``` * 對 解法 I 進行的修改: * `dp` array 由 2D 改為 1D * 將 解法 I 的表格 一列一列計算完成並覆蓋舊資料 * 將三個 `for` 迴圈 合併成 一個 * 程式碼解析: ```cpp=11 for (int i = 0; i <= n; i++) { for (int j = 0; j <= m; j++) { if (i == 0 && j == 0) continue; int p = RRR; if (i == 0) dp[j] = dp[j - 1] & (s2[j - 1] == s3[p]); else { dp[j] &= (s1[i - 1] == s3[p]); if (j > 0) dp[j] |= dp[j - 1] & (s2[j - 1] == s3[p]); } } } ``` * `#16`、`#17` 等價於 解法 I 的 ```cpp=15 for(int j = 1; j < (m + 1); ++j) dp[0][j] = dp[0][j - 1] && (s2[j - 1] == s3[j - 1]); ``` * `#19` 等價於 解法 I 的 ```cpp=12 for(int i = 1; i < (n + 1); ++i) dp[i][0] = dp[i - 1][0] && (s1[i - 1] == s3[i - 1]); ``` * `#20`、`#21` 等價於 解法 I 的 ```cpp=21 dp[i][j] = (dp[i - 1][j] && (s1[i - 1] == s3[i + j - 1])) || (dp[i][j - 1] && (s2[j - 1] == s3[i + j - 1])); ``` * 故 **RRR = i + j - 1** * [效能評估](https://leetcode.com/submissions/detail/414112144/): ![](https://i.imgur.com/kqpxhPd.png) * 與 解法 I 沒什麼區別 * 或許可將 dp 改用 Bit Array 儲存 ## Q5. 進階題 #### 解法 III : DP with Bit Array ```cpp __uint128_t MAX = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; bool isInterleave(char *s1, char *s2, char *s3) { size_t n = strlen(s1), m = strlen(s2); if ((n + m) != strlen(s3)) return false; __uint128_t dp = 1; for (int i = 0; i <= n; i++) { for (int j = 0; j <= m; j++) { if (i == 0 && j == 0) continue; int p = i + j -1; __uint128_t mask = MAX ^ ((__uint128_t)1 << j); if (i == 0){ __uint128_t set = (1U & (dp >> (j - 1))) & (s2[j - 1] == s3[p]); dp = (dp & mask) | (set << j); // dp[j] = dp[j - 1] & (s2[j - 1] == s3[p]); } else { __uint128_t set = (s1[i - 1] == s3[p]); dp &= mask | (set << j); // dp[j] &= (s1[i - 1] == s3[p]); if (j > 0){ set = (1U & (dp >> (j - 1))) & (s2[j - 1] == s3[p]); dp |= (set << j); // dp[j] |= dp[j - 1] & (s2[j - 1] == s3[p]); } } } } return (dp >> m); } ``` * [效能評估](https://leetcode.com/submissions/detail/414854880/): ![](https://i.imgur.com/SNCYGtS.png)