contributed by < Rsysz
>
sysprog
LeetCode 461. Hamming Distance 提及,兩個整數間的 Hamming distance 為其二進位的每個位元的差。請計算輸入參數兩整數 x 與 y 的 Hamming distance,例如整數 1
的二進位為 0 0 0 1
,而整數 4
的二進位為 0 1 0 0
,則 1
與 4
的 Hamming distance 為 2
。
運用第 3 周測驗 提到的 __builtin_popcount
(gcc extension 之一),我們可實作如下:
因 Hamming Distance
取兩者不同的位數用來計算距離,因此OP = (c) ^
如同 quiz3
一樣,每個 byte
獨立計算 1
的數量再予以加總輸出
Leetcode 477. Total Hamming Distance
最簡單的寫法就是每兩兩計算一次 hammingDistance
,然後很不意外的 TLE
了,代表更有效率的解法是存在的,因此我們先把範例中的數值列出以表格做觀察
Number | Bits |
---|---|
4 | 0100 |
14 | 1110 |
2 | 0010 |
hammingDistance
就是求取兩兩不同的 bit
數目的合,因此可以發現若將每位 bit
裡 0
與 1
的數目分別加總並相乘,剛好會等於 totalHammingDistance
因此我們可以將程式改寫為這樣
因雜訊,碟片損傷等,資料傳輸過程中需要面對部分資訊遺失或錯誤的風險,因此錯誤更正碼應運而生,錯誤更正碼的精神在如何確保資訊正確的情況下,盡量減少 redundant bits
的使用。
如下圖所示以 (15, 11) Hamming code
為例,透過 1, 2, 4, 8
, 4 個 redundant bits
也就是 Q1
~ Q4
的相互比較確認 1bit
錯誤發生的地方,可以對 15 bits
內發生的 1 bit
進行糾正,但若發生兩個 bits
以上的錯誤便無法偵測
但若使用 0
號 bit 檢查整體的 1-bits 是否為偶數,雖然無法進行更正,但能夠檢測出兩個 bits
以上的錯誤,是為 extended Hamming code
LeetCode 1483. Kth Ancestor of a Tree Node 大意:
給你一棵樹,樹上有 n 個節點,編號自
。樹以父節點陣列的形式提供,其中parent[i]
是節點 i 的父節點。樹的根節點是編號為 0 的節點。請設計treeAncestorGetKthAncestor(TreeAncestor *obj, int node, int k)
函式,回傳節點 node 的第 k 個祖先節點。若不存在這樣的祖先節點,回傳-1
。樹節點的第 k 個祖先節點是從該節點到根節點路徑上的第 k 個節點
Input:
Output:
本題透過建立 Ancestor Table
儲存每個節點的每以上父代直到 root
也就是 0
,因此可以判斷 parent
為二維陣列,AAA = (b) int **parent
程式運行到這行之後便會建立如下圖所示的 Ancestor Table
, [i]
為 row
而 [j]
為 column
parent[i][j] | [0] | [1] | [2] | [3] |
---|---|---|---|---|
[0] | -1 | -1 | -1 | -1 |
[1] | 0 | -1 | -1 | -1 |
[2] | 0 | -1 | -1 | -1 |
[3] | 1 | -1 | -1 | -1 |
[4] | 1 | -1 | -1 | -1 |
[5] | 2 | -1 | -1 | -1 |
[6] | 2 | -1 | -1 | -1 |
但這張 Ancestor Table
缺少祖父輩以上節點的資訊,若以此進行查詢將會花費過多的時間,因此透過下方程式碼更新 Ancestor Table
,BBB = (b) (-1)
parent[i][j] | [0] | [1] | [2] | [3] |
---|---|---|---|---|
[0] | -1 | -1 | -1 | -1 |
[1] | 0 | -1 | -1 | -1 |
[2] | 0 | -1 | -1 | -1 |
[3] | 1 | 0 | -1 | -1 |
[4] | 1 | 0 | -1 | -1 |
[5] | 2 | 0 | -1 | -1 |
[6] | 2 | 0 | -1 | -1 |
以 GetKthAncestor(5, 2)
為例,目標在於找尋 5
節點的上 2
代,也就是上代,觀察 node = obj->parent[node][i]
與上方 Ancestor Table
,因 Table
內直接存有目標節點,又因此最有效率的找法就是直接令 node = parent[5][1]
代表 if (k & (CCC))
必須在 i = 1
時成立,其餘則不成立,以 Bits
型式觀察 2
,可以得到
CCC = (f) 1 << i
。
Number | Bits |
---|---|
2 | 0010 |
將原來 parent[i][j]
型式翻轉為 parent[j][i]
,這個小改動可以使得原來
改動為
減少存取陣列元素的次數,也可以避免重複對陣列寫入數值,此外因原始程式碼中沒有對
中的 j
設邊界條件,此舉雖然減少了平衡樹 assign
的數量,但也因此需要對 max_level
額外加 1
來避免 j
的越界存取。
舉例來說,以 [5, [-1, 0, 1, 2, 3]]
代入,若 max_level
沒有加 1
的話, j
便會存取到 Highligh 部分的記憶體,從而導致 heap-buffer-overflow
。
parent[i][j] | [0] | [1] | [2] | [3] |
---|---|---|---|---|
[0] | -1 | -1 | -1 | -1 |
[1] | 0 | -1 | -1 | -1 |
[2] | 1 | 0 | -1 | -1 |
[3] | 2 | 1 | -1 | -1 |
[4] | 3 | 2 | 0 | -1 |
貌似簡單卻蘊含實作深度的 FizzBuzz 題目:
觀察 printf
的(格式)字串,可分類為以下三種:
is_divisible
: 透過先前 quiz2 學過的 fast divide
技巧,快速判斷 i
是否為 3
或 5
的倍數。
length有三種情形
i
是 15
的倍數, length = 8
i
是 3
或 5
的倍數,且非 15
的倍數, length = 4
i
非 3
或 5
的倍數, length = 2
且根據前面提到的印出值因此,當 i
有因數 3
時 MSG_LEN
要右移 3
以上,當 i
有因數 5
時 MSG_LEN
要右移 1
,答案裡所有組合只有 KK1 = (a) div5
, KK2 = (d) div3
, KK3 = (c) 2
能達到目的。
將兩者 printf
修改為 sprintf
後以 sudo perf stat
量測兩者的差異,因為兩者最大的差異應是,進行大數除法所耗費的時間,因此將輸入 i
設定為從30億到40億
但沒想到 naive
耗費的時間遠小於 bitwise
,然而在 branch-misses
上因 bitwise
是使用 branchless
的寫法,因此有所改善。
考慮到整數 PAGE_QUEUES
可能有以下數值:
, n from 1 to 14
給定 size_t offset
,試著將原本運算:
由於 PAGE_QUEUES
的數值分佈已知,在整數除法時,可思考加速運算的機制。
我們可預先進行計算,從而避免整數除法指令的使用: (假設在 x86_64 硬體執行 GNU/Linux 並透過 gcc/clang 編譯)
其中 CASES 可能形如:
請用最少的 case-break 敘述做出同等 size_t blockidx = offset / PAGE_QUEUES;
效果的程式碼。
因 PAGE_QUEUES
為某數因此,可將整體部分先行提出並先進行除法再除以剩下的數加速運算過程。
首先透過 offset >> ffs32(divident)
將, PAGE_QUEUES
數值中 先行除去,再以 switch-case 除去剩下的部分,因,可提出,又 因此最終剩下
CASES = (b) 3
, (d) 5
, (f) 7
N-Queens
問題是非常有名的題目,早在高中時期就聽過有這東西,不過懶散的我一直沒去深度鑽研這題目的奧妙,回歸正題首先我們知道這題有三個需要回傳的數值,分別是:
int *returnSize
: 代表回傳陣列 ans
的長度。int **returnColumnSizes
: 代表回傳陣列 ans[i]
, 0 < i < returnSize
的長度。char ***ans
: 回傳陣列本身。此外還要求每個陣列都必須使用 malloc
取得,參考RinHizakura大神的筆記可以知道, n > 18
時解的數量會超過 int
能容納的上限,因此可以將 sizes[]
先行列出以確保 malloc
到足夠的空間,避免重複呼叫 realloc
。
為了加速運算,使用 Bitwise
操作
upperlim
: 代表棋盤大小vd
: 代表垂直上的限制ld
: 代表右上到左下斜線上的限制rd
: 代表左上到右下斜線上的限制如下圖所示 藍色是 ld
,紅色是 rd
透過由下往上不斷遞迴擺放,根據棋盤限制,窮舉出每種解並放入 ans
。
Leetcode Runtime Error
另外比較值得注意的是如果有使用全域或以靜態宣告的變數時,必須在每次進入函數時對其初始化,否則就會遇上等問題,因為 Leetcode
使用同一個實體對所有 test case
做測試,而這些變數會影響到下個 test cas
的運行結果。