contributed by < Veternal1226
>
sysprog2020
1
LeetCode 835. Image Overlap 給定二個影像 A 和 B,二者皆為大小相同的二維正方形矩陣,內部以二進位表示。我們轉換其中一個影像,向左、右、上,或下滑動任意數量的單位,並將其置於另一個影像之上,隨後,該轉換的重疊 (overlap) 指二個影像都有 1 的位置數量,注意:轉換不包含向任一方向旋轉。於是,最大可能的重疊為何?
範例一:
範例 1:
輸入
輸出:
解釋
思路:找出所有 1 的位置,對兩個矩陣的所有這些位置進行求差,再統計這些差出現最多的次數。兩個坐標的差表示什麼?即是將其中一個坐標移動到另一個坐標需要移動的向量。於是,在走訪個別單元的過程中,我們找出 A 中所有值為 1 的坐標,移動到 B 中所有值為 1 的坐標需要移動的向量。於是乎,在這些向量中出現次數最多的向量,就是我們要求的整個矩陣應該移動的向量。
以下程式碼針對 LeetCode 835. Image Overlap,提出可能的解法:
請補完程式碼,使其符合 LeetCode 題目要求。
參考資料:
作答區
ITER1 = (c)
(a)
for (int k = 0; k < n - j; k++) res[k + j] = res[k]
(b)
for (int k = 0; k <= n - j; k++) res[k + j] = res[k]
(c)
for (int k = n - j - 1; k >= 0; k--) res[k + j] = res[k]
(d)
for (int k = n - j; k >= 0; k--) res[k + j] = res[k]
ITER2 = (b)
(a)
for (int k = j; k < n; k++) res[k + j] = res[k]
(b)
for (int k = j; k < n; k++) res[k - j] = res[k]
(c)
for (int k = j - 1; k < n; k++) res[k + j] = res[k]
(d)
for (int k = j - 1; k < n; k++) res[k - j] = res[k]
延伸題目:
int largestOverlap()
47~50 行為初始化
51~58 行將原本題目提供的 bitmap 轉成一維的陣列,將 bitmap 以 int 格式儲存
60~67 行測試所有平移結果,找出重疊的最大值
64 行的 translate()
將原本的陣列做平移
65 行的 overlap_count()
用來算出重疊量,並檢查是否需更新最大值
static void translate()
21 行的 mask
用來取出右移後真正位於比較空間內的 bits,比較空間外的會被 mask
清為 0
23~30 行進行左右平移 (x軸)
32~41 行進行上下平移 (y軸)
y軸範圍參考此圖
j >= 0
:
因此在 j >= 0
的情況下
範圍為 (n-1-j) ~ 0
搬移方式為 res[k + j] = res[k]
所以 ITER1 = (c)
for (int k = n - j - 1; k >= 0; k--) res[k + j] = res[k]
j < 0
:
因此在 j < 0
的情況下
範圍為 (-j) ~ (n-1)
搬移方式為 res[k + j] = res[k]
但因題目先將 j = -j
因此範圍改為 j ~ (n-1)
搬移方式為 res[k - j] = res[k]
所以 ITER2 = (b)
for (int k = j; k < n; k++) res[k - j] = res[k]
popcount()
方式計算 1
的數量,計算行數與原矩陣 大小相同 。 (此處即為可優化的部分)延伸題目:
2. 提出效率更高,且空間佔用更少的 C 程式碼實作
主要更改處為取消 34、35、39、40 行的補0行為,把 37 行的 -j 改成 j 方便理解,並將 overlap_count 的比較範圍縮小。
舉例圖解:
藍色陣列為平移前,紅色陣列為平移後,綠框表有興趣之區域 ( 搬移基準點或比較範圍 )。
以 n = 8, i = 2, j = 2
為例:
搬移過程:
搬移順序為從 y = n-j-1 行開始至 y= 0 (方向往上),往右移2單位、下移2單位
比較範圍:
優化後比較範圍:
以 n=8, i=-2, j=-2 為例:
搬移過程:
搬移順序為從 y = 0-j (同義於 -j ) 行開始至 y= n-1 (方向往下),往左移2單位、上移2單位
比較範圍:
優化後比較範圍:
原本方法記憶體占用:
優化比較範圍後記憶體占用:
優化後程式碼:
2
LeetCode 1569. Number of Ways to Reorder Array to Get Same BST 給定一個陣列 nums 表示 到 的排列,依據元素在 nums
中的順序,依次插入一個初始為空的二元搜尋樹 (BST)。請你統計將 nums
重新排序後,滿足以下條件的個數:
例如,給定
,我們得到一棵 2
為 root、1
為 left 及 3
為 right 的樹。
陣列 也能得到相同的 BST,但 會得到一棵不同的 BST 。
由於答案可能會很大,請將結果對 取餘數。
範例 1
1
nums
重排, 可得到相同的 BST範例 2
5
範例 3
0
範例 4
19
範例 5
216212978
3216212999
216212978
根節點是陣列第一個數,分為左右兩個子樹,左右子樹之間的順序若不變即可
假設左子樹 長度 ,右子樹 長度 ,則有效組合數為
DECL = (d)
(a)
int cache[N][N]
(b)
int cache[N - 1][N - 1]
(c)
int cache[N + 1][N]
(d)
int (*cache)[N + 1]
(e)
int (*cache)[N]
INIT = (a)
(a)
comb[i][j] = (comb[i - 1][j] + comb[i - 1][j - 1]) % MOD
(b)
comb[i][j] = (comb[i - 1][j] + comb[i - 1][j - 1])
(c)
comb[i][j] = (comb[i][j] + comb[i - 1][j - 1]) % MOD
(d)
comb[i][j] = (comb[i][j] + comb[i - 1][j - 1])
(e)
comb[i][j] = (comb[i][j - 1] + comb[i - 1][j - 1]) % MOD
(f)
comb[i][j] = (comb[i][j - 1] + comb[i - 1][j - 1])
從提示與花花醬的教學中,可了解此題的解題關鍵為:
根節點是陣列第一個數,分為左右兩個子樹,左右子樹之間的順序若不變即可
假設左子樹 長度 ,右子樹 長度 ,則有效組合數為
題目所挖空的部分為取組合的部分 ()
目的為先把組合的結果算出並建好表格,使每次取組合時不用重算
延伸題目:
根據花花醬的教學,此表格是算出 pascal's triangle 中各元素的值。
因此 INIT 應選 (a) comb[i][j] = (comb[i - 1][j] + comb[i - 1][j - 1]) % MOD
29 行: 進入 recursive() 遞迴函式,進入遞迴時,將組合表表一起傳入,從 20 行可看出組合表的大小為 [N+1][N+1]
DECL選項中只有 (d)
int (*cache)[N + 1]
符合,故選 (d)
size<=2
對取餘數原因:
hash成本太高,不想太複雜操作 (需考慮碰撞、hash function 等)
單純取餘數會有不夠分散的問題 (ex: 360%2=0,結果集中在小數值不夠分散)
因此採用對大質數取餘數的方式實作
延伸題目:
2. 提出效率更高,且空間佔用更少的 C 程式碼實作
從花花醬的教學中得知建立組合表方式是算出 pascal's triangle 中各元素的值,而 pascal's triangle 並不會用到右上方的三角形。
comb在矩陣中的儲存方式:
右上角紅色三角形表示不會使用到的位置
原本建表過程中的第 26 行如下
可看出其計算了整張表的值,將 j 的範圍改成 j <= i
能夠節省計算右上三角形的值花費的計算成本。實際改動如下列程式碼。
優化前執行時間:
優化後執行時間:
3
在 Ubuntu/Debian Linux 中,可執行 sudo apt install dictionaries-common 以安裝辭典套件,於是可得到 /usr/share/dict/words 檔案,後者包含超過 10 萬個單字/詞:
考慮以下 C 程式 (strsearch.c) 可從給定的詞典中檢索:
給定測試輸入 (檔名: inputs) 如下:
測試方式:
預期執行結果:
請補完程式碼。
P1 = (d)
(a)
prev_start + i
(b)
prev_start + i + 1
(c)
i - prev_start
(d)
i - prev_start + 1
P2 = (b)
(a)
i
(b)
i + 1
(c)
i - 1
可從這兩段程式碼中得知
116行的行為為: 將起始點 prev_start 與長度 P1 傳入 htable_insert
117行的行為為: 更新起始點 prev_start
圖解:
將起始位址 prev_start - 0
與長度 i - prev_start +1
作為參數輸入 htable_insert
中,再將 prev_start
更新到 i+1
的位址,所以題目要求的程式碼應如下:
因此,P1應選 (d)
i - prev_start + 1
, P2應選 (b)
i + 1
。
延伸題目:
static void htable_insert()
將新的詞輸入至 htable 與 clist 中以方便查詢與比較
62 行: 取得 hash 值
63 行: 記錄該詞的開頭位置,放入 clist.fpos
64 行: 計算該詞的長度放入 clist.len
65 行: 記錄 htable 中該詞位置的原有指標,使用 link list 的方式處理碰撞
66 行: 更新 htable 該詞位置上的 list 長度
static bool htable_lookup()
查詢 htable 中是否存在輸入的詞
72 行: 取得 hash 值
73 行: 取得 htable 中記錄的指標
74 行: 取得 htable 中下個記錄的指標
75~80 行: 查詢 cclist 中是否有輸入的詞出現,並回傳結果
int main()
85~88 行: 檢查參數格式是否正確
91~93 行: 開啟 argv[1]
代表的檔案以作為辭典檔輸入
95~100 行: 將開啟檔案的 file descriptor轉成 stat 格式,利用 stat 資訊使用 mmap()
將辭典檔映射到記憶體,把檔案操作變成記憶體操作。
使用mmap原因: I/O最佳化
把檔案操作變成記憶體操作
檔案操作 (fopen): 詞典檔案太大時啟動時要讀很久,但只需開啟檔案一次。
記憶體操作 (mmap): 對應到記憶體,發生page fault時才需要讀取成本,但讀取次數隨 page fault 次數增加。
103~107 行: 計算辭典檔行數
109~111 行: 宣告空間放置 htable 與 clist
113~119 行: 利用 \n
做斷詞依據,將每個字放入 htable 中,放入 htable 位置的依據為 hash function (此題使用 DJB Hash Function)
122~134 行: 使用 collision list方式處理碰撞
138 行: 初始化 fgetd 需要的 buffer 空間
140~147 行: 從標準輸入查詢各字串段是否位於 hash function 中
141 行: 從標準輸入輸入至 req_buf 中
142 行: 計算輸入的字串長度
144~146 行: 查詢 htable 內能不能查詢到輸入的詞
附註:
hash 存在的原因是為了減少重複查詢的查詢成本。
有驗證過在 ubuntu 20 環境下此程式可正確執行。
延伸題目:
2. 提出效率更高,且空間佔用更少的 C 程式碼實作
//TODO