百度2014研發類校園招聘筆試題解答

先整體說下題型,共有3道簡答題,3道算法編程題和1道系統設計題,題目有難有易,限時兩小時完成。
 
1、簡答題

1. 動態連接庫和靜態連接庫的優缺點php

2. 輪詢任務調度和可搶佔式調度有什麼區別?web

3. 列出數據庫中經常使用的鎖及其應用場景算法

 

2、算法設計題

1. 給定N是一個正整數,求比N大的最小「不重複數」,這裏的不重複是指沒有兩個相等的相鄰位,如1102中的11是相等的兩個相鄰位故不是不重複數,而12301是不重複數。數據庫

2. 設N是一個大整數,求長度爲N的字符串的最長迴文子串。編程

3. 座標軸上從左到右依次的點爲a[0]、a[1]、a[2]……a[n-1],設一根木棒的長度爲L,求L最多能覆蓋座標軸的幾個點?數組

 

3、系統設計題

1. 在現代系統的設計過程當中,爲了減輕請求的壓力,一般採用緩存技術,爲了進一步提高緩存的命中率,同常採用分佈是緩存方案。調度模塊針對不一樣內容的用戶請求分配給不一樣的緩存服務器向用戶提供服務。請給出一個分佈式緩存方案,知足以下要求:緩存

1) 單臺緩存服務器故障,整個分佈式緩存集羣,能夠繼續提供服務。服務器

2)經過必定得分配策略,能夠保證充分利用每一個緩存服務的存儲空間,及負載均衡。當部分服務器故障或系統擴容時,改分配策略能夠保證較小的緩存文件重分配開銷。網絡

3)當不一樣緩存服務器的存儲空間存在差別時,分配策略能夠知足比例分配。併發


 
下面給出我本身的一些解答,不保證100%正確,歡迎批評指正。
 

1、簡答題

1. 動態連接庫和靜態連接庫的優缺點
 
解答:(1) 動態連接庫(Dynamic Linked Library):Windows爲應用程序提供了豐富的函數調用,這些函數調用都包含在動態連接庫中。其中有3個最重要的DLL,Kernel32.dll、 User32.dll和GDI32.dll。有兩種使用方式:一種是靜態加載,即在應用程序啓動時被加載;一種是動態加載,便是該動態連接庫在被使用時才被應用程序加載。優勢以下:

a. 共享:多個應用程序可使用同一個動態庫,啓動多個應用程序的時候,只須要將動態庫加載到內存一次便可;
b. 開發模塊好:要求設計者對功能劃分的比較好。

缺點是不能解決引用計數等問題。

(2)靜態庫(Static Library):函數和數據被編譯進一個二進制文件(一般擴展名爲.LIB)。在使用靜態庫的狀況下,在編譯連接可執行文件時,連接器從庫中複製這些函數和數據並把它們和應用程序的其它模塊組合起來建立最終的可執行文件(.EXE文件)。靜態連接庫做爲代碼的一部分,在編譯時被連接。優缺點以下:
代碼的裝載速度快,由於編譯時它只會把你須要的那部分連接進去,應用程序相對比較大。可是若是多個應用程序使用的話,會被裝載屢次,浪費內存。

 
2. 輪詢任務調度和可搶佔式調度有什麼區別?
 
解答:(1)輪詢調度的原理是每一次把來自用戶的請求輪流分配給內部中的服務器,從1開始,直到N(內部服務器個數),而後從新開始循環。 只有在當前任務主動放棄CPU控制權的狀況下(好比任務掛起),才容許其餘任務(包括高優先級的任務)控制CPU。其優勢是其簡潔性,它無需記錄當前全部鏈接的狀態,因此它是一種無狀態調度。但不利於後面的請求及時獲得響應。
(2)搶佔式調度容許高優先級的任務打斷當前執行的任務,搶佔CPU的控制權。這有利於後面的高優先級的任務也能及時獲得響應。但實現相對較複雜且可能出現低優先級的任務長期得不到調度。
 
3. 列出數據庫中經常使用的鎖及其應用場景
 
解答:數據庫中的鎖是網絡數據庫中的一個很是重要的概念,它主要用於多用戶環境下保證數據庫完整性和一致性。各類大型數據庫所採用的鎖的基本理論是一致的,但在具體實現上各有差異。目前,大多數數據庫管理系統都或多或少具備自我調節、自我管理的功能,所以不少用戶實際上不 清楚鎖的理論和所用數據庫中鎖的具體實現。在數據庫中加鎖時,除了能夠對不一樣的資源加鎖,還可使用不一樣程度的加鎖方式,即鎖有多種模式,SQL Server中鎖模式包括:  

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」選項設定批量修改鎖。  

 

2、算法設計題

1. 給定N是一個正整數,求比N大的最小「不重複數」,這裏的不重複是指沒有兩個相等的相鄰位,如1102中的11是相等的兩個相鄰位故不是不重複數,而12301是不重複數。
 
算法思想:固然最直接的方法是採用暴力法,從N+1開始逐步加1判斷是不是不重複數,是就退出循環輸出,這種方法通常是不可取的,例如N=11000000,你要一個個的加1要加到12010101,一共循環百萬次,每次都要重複判斷是不是不重複數,效率極其低下,所以是不可取的。這裏我採用的方法是:從N+1的最高位往右開始判斷與其次高位是否相等,若是發現相等的(即爲重複數)則將次高位加1,注意這裏可能進位,如8921—>9021,後面的直接置爲010101...形式,如1121—>1201,此時便完成「不重複數」的初步構造,但此時的「不重複數」不必定是真正的不重複的數,由於可能進位後的次高位變爲0或進位後變成00,如9921—>10001,此時須要再次循環判斷從新構造直至知足條件便可,這種方法循環的次數比較少,能夠接受。
 
Source Code:
// 求比指定數大且最小的「不重複數」

#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;
}

 
2. 設N是一個大整數,求長度爲N的字符串的最長迴文子串。
 
算法1:第一個方法固然是暴力法,外面的兩層循環找到全部子串,第三層循環判斷子串是不是迴文。方法的時間複雜度爲O(n^3),空間複雜度爲O(1)。
 
算法2:採用動態規劃法判斷子串是不是迴文。開闢一個P[i][j]用來表示str[i..j]是否爲迴文,P[i][j]的狀態轉移方程以下:
  1. 當i==j時,P[i][j]=true

  2. 當i+1==j時,P[i][j]=str[i]==str[j]

  3. 其餘,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) 分開理解就是:

  1. 若是mx - i > P[j], 則P[i]=P[j]

  2. 不然,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;
}
 
3. 座標軸上從左到右依次的點爲a[0]、a[1]、a[2]……a[n-1],設一根木棒的長度爲L,求L最多能覆蓋座標軸的幾個點?
 
算法思想:開始時我把題目理解錯了,覺得是求a中最大子序列和使其等於L,其實是求知足a[j]-a[i] <= L && a[j+1]-a[i] > L這兩個條件的j與i中間的全部點個數中的最大值,即j-i+1最大,這樣題目就簡單多了,方法也很簡單:直接從左到右掃描,兩個指針i和j,i從位置0開始,j從位置1開始,若是a[j] - a[i] <= L則j++並記錄中間通過的點個數,若是a[j] - a[i] > L則j--回退,覆蓋點個數-1回到恰好知足條件的時候,將知足條件的最大值與所求最大值比較,而後i++,j++直到求出最大的點個數。

有兩點須要注意:

(1)這裏可能沒有i和j使得a[j] - a[i]恰好等於L的,因此判斷條件不能爲a[j] - a[i] = L。
(2)可能存在不一樣的覆蓋點但覆蓋的長度相同,此時只選第一次覆蓋的點。
 
Source code:
// 求最大覆蓋點

#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;
}

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息