1. 動態鏈接庫和靜態鏈接庫的優缺點
2. 輪詢任務調度和可搶占式調度有什么區別?
3. 列出數據庫中常用的鎖及其應用場景
1. 給定N是一個正整數,求比N大的最小“不重復數”,這里的不重復是指沒有兩個相等的相鄰位,如1102中的11是相等的兩個相鄰位故不是不重復數,而12301是不重復數。
2. 設N是一個大整數,求長度為N的字符串的最長回文子串。
3. 坐標軸上從左到右依次的點為a[0]、a[1]、a[2]……a[n-1],設一根木棒的長度為L,求L最多能覆蓋坐標軸的幾個點?
1. 在現代系統的設計過程中,為了減輕請求的壓力,通常采用緩存技術,為了進一步提升緩存的命中率,同常采用分布是緩存方案。調度模塊針對不同內容的用戶請求分配給不同的緩存服務器向用戶提供服務。請給出一個分布式緩存方案,滿足如下要求:
1) 單台緩存服務器故障,整個分布式緩存集群,可以繼續提供服務。
2)通過一定得分配策略,可以保證充分利用每個緩存服務的存儲空間,及負載均衡。當部分服務器故障或系統擴容時,改分配策略可以保證較小的緩存文件重分配開銷。
3)當不同緩存服務器的存儲空間存在差異時,分配策略可以滿足比例分配。
一、簡答題
a. 共享:多個應用程序可以使用同一個動態庫,啟動多個應用程序的時候,只需要將動態庫加載到內存一次即可;
b. 開發模塊好:要求設計者對功能划分的比較好。
缺點是不能解決引用計數等問題。
(2)靜態庫(Static Library):函數和數據被編譯進一個二進制文件(通常擴展名為.LIB)。在使用靜態庫的情況下,在編譯鏈接可執行文件時,鏈接器從庫中復制這些函數和數據並把它們和應用程序的其它模塊組合起來創建最終的可執行文件(.EXE文件)。靜態鏈接庫作為代碼的一部分,在編譯時被鏈接。優缺點如下:
代碼的裝載速度快,因為編譯時它只會把你需要的那部分鏈接進去,應用程序相對比較大。但是如果多個應用程序使用的話,會被裝載多次,浪費內存。
1)共享鎖
SQL Server中,共享鎖用於所有的只讀數據操作。共享鎖是非獨占的,允許多個並發事務讀取其鎖定的資源。默認情況下,數據被讀取后,SQL Server立即釋放共享鎖。例如,執行查詢“SELECT * FROM my_table”時,首先鎖定第一頁,讀取之后,釋放對第一頁的鎖定,然后鎖定第二頁。這樣,就允許在讀操作過程中,修改未被鎖定的第一頁。但是,事務 隔離級別連接選項設置和SELECT語句中的鎖定設置都可以改變SQL Server的這種默認設置。例如,“ SELECT * FROM my_table HOLDLOCK”就要求在整個查詢過程中,保持對表的鎖定,直到查詢完成才釋放鎖定。
2)修改鎖
修 改鎖在修改操作的初始化階段用來鎖定可能要被修改的資源,這樣可以避免使用共享鎖造成的死鎖現象。因為使用共享鎖時,修改數據的操作分為兩步,首先獲得一 個共享鎖,讀取數據,然后將共享鎖升級為獨占鎖,然后再執行修改操作。這樣如果同時有兩個或多個事務同時對一個事務申請了共享鎖,在修改數據的時候,這些 事務都要將共享鎖升級為獨占鎖。這時,這些事務都不會釋放共享鎖而是一直等待對方釋放,這樣就造成了死鎖。如果一個數據在修改前直接申請修改鎖,在數據修 改的時候再升級為獨占鎖,就可以避免死鎖。修改鎖與共享鎖是兼容的,也就是說一個資源用共享鎖鎖定后,允許再用修改鎖鎖定。
3)獨占鎖
獨占鎖是為修改數據而保留的。它所鎖定的資源,其他事務不能讀取也不能修改。獨占鎖不能和其他鎖兼容。
4)結構鎖
結構鎖分為結構修改鎖(Sch-M)和結構穩定鎖(Sch-S)。執行表定義語言操作時,SQL Server采用Sch-M鎖,編譯查詢時,SQL Server采用Sch-S鎖。
5)意向鎖
意 向鎖說明SQL Server有在資源的低層獲得共享鎖或獨占鎖的意向。例如,表級的共享意向鎖說明事務意圖將獨占鎖釋放到表中的頁或者行。意向鎖又可以分為共享意向鎖、 獨占意向鎖和共享式獨占意向鎖。共享意向鎖說明事務意圖在共享意向鎖所鎖定的低層資源上放置共享鎖來讀取數據。獨占意向鎖說明事務意圖在共享意向鎖所鎖定 的低層資源上放置獨占鎖來修改數據。共享式獨占鎖說明事務允許其他事務使用共享鎖來讀取頂層資源,並意圖在該資源低層上放置獨占鎖。
6)批量修改鎖
批量復制數據時使用批量修改鎖。可以通過表的TabLock提示或者使用系統存儲過程sp_tableoption的“table lock on bulk load”選項設定批量修改鎖。
二、算法設計題
// 求比指定數大且最小的“不重復數” #include <stdio.h> void minNotRep(int n) { // 需要多次判斷 while(1) { int a[20], len = 0, i, b = 0; // flag為true表示是“重復數”,為false表示表示是“不重復數” bool flag = false; // 將n的各位上數字存到數組a中 while(n) { a[len++] = n % 10; n = n / 10; } // 從高位開始遍歷是否有重復位 for(i = len - 1; i > 0; i--) { // 有重復位則次高位加1(最高位有可能進位但這里不需要額外處理) if(a[i] == a[i - 1] && !flag) { a[i - 1]++; flag = true; } else if(flag) { // 將重復位后面的位置為0101...形式 a[i - 1] = b; b = (b == 0) ? 1 : 0; } } // 重組各位數字為n,如果是“不重復數”則輸出退出否則繼續判斷 for(i = len - 1; i >= 0; i--) { n = n * 10 + a[i]; } if(!flag) { printf("%d\n", n); break; } } } int main() { int N; while(scanf("%d", &N)) { minNotRep(N + 1); } return 0; }
-
當i==j時,P[i][j]=true
-
當i+1==j時,P[i][j]=str[i]==str[j]
-
其他,P[i][j]=P[i+1][j-1]&&(str[i]==str[j])
那么P[i][j]中j-i+1最大的且值為true的就是最長回文子串。這樣,這個方法的時間復雜度為O(n^2),空間復雜度為O(n^2)。比暴力法有很大的改進。
Source Code:
#include <stdio.h> #include <stdlib.h> #include <string.h> int longestPalSubstr(char *str) { int n = strlen(str); int i, j, len, maxlen = 0, maxi = 0, maxj = 0; bool **P = (bool**)malloc(sizeof(bool) * n); for(i = 0; i < n; i++) { P[i] = (bool*)malloc(sizeof(bool) * n); } // initialize P[i][i] for(i = 0; i < n; i++) { P[i][i] = true; } // compute P[n][n] by length for(len = 2; len <= n; len++) { for(i = 0; i < n - len + 1; i++) { j = i + len - 1; if(len == 2) { P[i][j] = (str[i] == str[j]); } else { P[i][j] = ((str[i] == str[j]) && P[i + 1][j - 1]); } } } // int k; for(i = 0; i < n; i++) { for(j = i; j < n; j++) { // printf("%d ", P[i][j]); if(P[i][j] && maxlen < (j - i + 1)) { maxlen = j - i + 1; maxi = i; maxj = j; } } // printf("\n"); // for(k = 0; k <= i; k++) // printf(" "); } printf("The longest palin substr is "); for(i = maxi; i <= maxj; i++) { printf("%c", str[i]); } printf(", maxlen is %d\n\n", maxlen); return maxlen; } int main() { char str[100]; while(1) { gets(str); if(strlen(str) == 0) break; longestPalSubstr(str); } return 0; }
算法3:第三個方法,可以從上面那個方法的狀態轉移方程獲得啟發,對於每一個回文子串可以先確定一個中心,然后向兩邊擴展,這樣可以在時間復雜度O(n^2),空間復雜度O(1)的情況下完成,需要注意的是,長度為奇數和偶數的中心的情況是不同的。
Source Code:
#include <stdio.h> #include <stdlib.h> #include <string.h> int longestPalSubstr(char *str) { int len = strlen(str); int i, maxLen = 1, start = 0; int low, high; // 將每個字符作為中心向兩邊擴展判斷 for(i = 1; i < len; i++) { // 處理長度為偶數的情況 low = i - 1; high = i; while(low >= 0 && high < len && str[low] == str[high]) { if(maxLen < high - low + 1) { start = low; maxLen = high - low + 1; } low--; high++; } // 處理長度為奇數的情況 low = i - 1; high = i + 1; while(low >= 0 && high < len && str[low] == str[high]) { if(maxLen < high - low + 1) { start = low; maxLen = high - low + 1; } low--; high++; } } printf("The longest palin substr is "); for(i = start; i < start + maxLen; i++) { printf("%c", str[i]); } printf(", maxlen is %d\n\n", maxLen); return maxLen; } int main() { char str[100]; while(1) { gets(str); if(strlen(str) == 0) break; longestPalSubstr(str); } return 0; }
算法4:第四個方法采用后綴數組,將最長回文子串的問題轉化為最長公共前綴的問題。具體的做法就是:將整個字符串翻轉之后,拼接到原字符串后,注意用特殊字 符分開,這樣問題就變成了新的字符串的某兩個后綴的最長公共前綴的問題了。這個方法比較強大,很多字符串的問題都能夠巧妙的解決。不過實現起來也相對比較:難,好的實現和差的實現時間復雜度相差很大。由於對后綴數組不是很清楚,未寫代碼,等學習了后綴數組再過來補。
算法5:第五個方法叫做Manacher算法,是一種線性時間的方法,非常巧妙。首先,我們在上面的方法中個,都要考慮回文長度為奇數或者偶數的情況。這個:方法,引入一個技巧,使得奇數和偶數的情況統一處理了。具體做法如下:
abba轉換為#a#b#b#a#,也就是在每一個字符兩邊都加上一個特殊字符。
然后創建一個新的P[i]表示,以第i個字符為中心的回文字串的半徑。例如上面的例子,對應的P如下,設S為原始字符串:
S | # | a | # | b | # | b | # | a | # |
P | 1 | 2 | 1 | 2 | 5 | 2 | 1 | 2 | 1 |
通過觀察上面的表,大家可以發現P[i]-1就是實際回文字串的長度。如果知道P,遍歷一次就知道最長的回文子串。可以該如何計算P呢?這是這個算法最核心的部分。
下面的討論基本轉自博客:http://www.felix021.com/blog/read.php?2040 該博客中對Manacher算法介紹得也非常好,向大家推薦。
算法引入兩個變量id和mx,id表示最長回文子串的中心位置,mx表示最長回文字串的邊界位置,即:mx=id+P[id]。
在這里有一個非常有用而且神奇的結論:如果mx > i,那么P[i] >= MIN(P[2 * id - i], mx - i) 分開理解就是:
-
如果mx - i > P[j], 則P[i]=P[j]
-
否則,P[i] = mx - i.
這兩個該如何理解呢?具體的解釋請看下面的兩個圖。
(1)當 mx - i > P[j] 的時候,以S[j]為中心的回文子串包含在以S[id]為中心的回文子串中,由於 i 和 j 對稱,以S[i]為中心的回文子串必然包含在以S[id]為中心的回文子串中,所以必有 P[i] = P[j],見下圖。
(2)當 P[j] >= mx - i 的時候,以S[j]為中心的回文子串不一定完全包含於以S[id]為中心的回文子串中,但是基於對稱性可知,下圖中兩個綠框所包圍的部分是相同的,也就是 說以S[i]為中心的回文子串,其向右至少會擴張到mx的位置,也就是說 P[i] >= mx - i。至於mx之后的部分是否對稱,就只能老老實實去匹配了。
對於 mx <= i 的情況,無法對 P[i]做更多的假設,只能P[i] = 1,然后再去匹配了。
理解了上面的一點,就沒有問題了。
Source Code:
#include <stdio.h> #include <stdlib.h> #include <string.h> int longestPalSubstr(char *str) { char s[100]; int i, maxLen = 1, start = 0, j; int len = strlen(str); int mx = 0, id = 0, min; s[0] = '$'; s[1] = '#'; for(i = 0, j = 2; i < len; i++, j += 2) { s[j] = str[i]; s[j + 1] = '#'; } s[j] = '\0'; len = len * 2 + 1; int *p = (int *)malloc(sizeof(int) * len); memset(p, 0, len); p[0] = 1; for(i = 1; i < len; i++) { min = p[2 * id - i] > (mx - i) ? (mx - i) : p[2 * id - i]; p[i] = mx > i ? min : 1; while(s[i + p[i]] == s[i - p[i]]) { p[i]++; } if(i + p[i] > mx) { mx = i + p[i]; id = i; } } for(i = 0; i < len; i++) { //printf("%d ", p[i]); if(maxLen < p[i] - 1) { maxLen = p[i] - 1; start = i - maxLen; } } printf("The longest palin substr is "); for(i = start; i < start + 2 * maxLen + 1; i++) { if(s[i] != '#') { printf("%c", s[i]); } } printf(", maxlen is %d\n\n", maxLen); return maxLen; } int main() { char str[100]; while(1) { gets(str); if(strlen(str) == 0) break; longestPalSubstr(str); } return 0; }
有兩點需要注意:
// 求最大覆蓋點 #include <stdio.h> int maxCover(int a[], int n, int L) { int count = 2, maxCount = 1, start; int i = 0, j = 1; while(i < n && j < n) { while((j < n) && (a[j] - a[i] <= L)) { j++; count++; } // 退回到滿足條件的j j--; count--; if(maxCount < count) { start = i; maxCount = count; } i++; j++; } printf("covered point: "); for(i = start; i < start + maxCount; i++) { printf("%d ", a[i]); } printf("\n"); return maxCount; } int main() { // test int a[] = {1, 3, 7, 8, 10, 11, 12, 13, 15, 16, 17, 18, 21}; printf("max count: %d\n\n", maxCover(a, 13, 8)); int b[] = {1,2,3,4,5,100,1000}; printf("max count: %d\n", maxCover(b, 7, 8)); return 0; }