1. 程式人生 > >2013網易遊戲暑期實習生面試題

2013網易遊戲暑期實習生面試題

最近去網易遊戲面試,感覺自己水的一比,有兩道題沒回答出來,在網上搜索了一下解法。

1. 英文字串分詞,已知一長串字串,這是一個句子,但是單詞之間沒有空格,現有單詞本,問如何能把句子分詞。

/*

給你一個沒有間隔的字串“thisisasentence”,如何將他分割成如下的句子:“this is a sentence”。

提供一個函式用來檢驗一個字串是不是單詞:bool dic(char* w);

完成下列的函式。要求效率儘可能快。

bool Detect(char* str)
{

}

儘量寫出完整思路,最好有虛擬碼。

提示: 遞迴,回溯。這裡使用最長單詞優先匹配 + 深度優先搜尋+回溯的方法解決此問題。

其中資料來源為一篇普通的英文文字,測試時大概有幾千個英文單詞,先進行預處理,

得到長字串和單詞詞典。在實現時,由於使用的是stl的string,介面和題目中給出的有所處理,

但不影響解決該問題。本程式中 Go(str,startIdx) 意為對str(startIdx:)進行分詞

而bool dic(char * w)其實就是程式中對詞典map的find操作

*/


#include <iostream>
#include <string>
#include <vector>
#include <fstream>
#include <map>
using namespace std;
 

int maxWordLen = 0;
char puncs[] = {'.',',','-',')','(','[',']','\"'};
string splitResult[1000000];
bool bSuc = false;
//已分的詞的個數
int splittedWordNum = 0;
map<string,int> wordDic;

 

//判斷是否是標點

bool isPunc(char ch)
{
    for (int i = 0;i < sizeof(puncs);i++)
    {
        if (ch == puncs[i])
        {
            return true;
        }
    }
    return false;
}

 

//從檔案構造長字串和詞典

void ReadFromFile(const string & filePath,string& strSentence,
                  map<string,int>& wordDic)

{
    ifstream fin(filePath.c_str());
    string str;
    string word;
    int wordOccured = 0;
    while (fin >> str)
    {
        int firstIdx = 0;
        while(firstIdx < str.size() && isPunc(str[firstIdx]))
        {
            firstIdx++;
        }

        int secIdx = str.size() - 1;
        while(secIdx >=0 && isPunc(str[secIdx]))
        {
            secIdx --;
        }
        if (secIdx >= firstIdx)
        {
            word = str.substr(firstIdx,secIdx - firstIdx + 1);
            wordDic[word] = 1;
            strSentence = strSentence + word;
            if (secIdx - firstIdx + 1 > maxWordLen)
            {
                maxWordLen = secIdx - firstIdx + 1;
            }
            wordOccured++;
            //cout << word << " ";
        }
    }
    cout << wordOccured << endl;
    fin.close();
}

void PrintSplitResult()
{
    for (int i = 0;i<splittedWordNum;i++)
    {
        cout << splitResult[i] << " ";
    }
}

void Go(string & strSentence,int startIdx)
{
    //如果已經有分詞成功,則結束
    if (bSuc)
    {
        return;
    }
    //分詞完畢
    if (startIdx == strSentence.size())
    {
        PrintSplitResult();
        //cout << endl;
        //cout << splittedWordNum << endl;
        bSuc = true;
        return;
    }
     //否則從最長的詞開始匹配
    int maxLen = strSentence.size() - startIdx;
    if (maxLen > maxWordLen)
    {
        maxLen = maxWordLen;
    }
    for (int len = maxLen;len >0;len--)
    {
        string candidateWord = strSentence.substr(startIdx,len);
        //該詞存在於詞典
        if (wordDic.find(candidateWord) != wordDic.end())
        {
            splittedWordNum ++;
            splitResult[splittedWordNum - 1] = candidateWord;
            //遞迴對下標startIdx + len開頭的字串進行分詞
            Go(strSentence,startIdx + len);
            splittedWordNum --; // 這裡需要回溯
        }
    }    
}
int main(int argc, char* argv[])
{
    string strSentence;
    string filePath = "in.txt";
    ReadFromFile(filePath,strSentence,wordDic);  
     Go(strSentence,0); 
    //cout << wordDic.size() << endl;
    //cout << splittedWordNum << endl;
    if (!bSuc)
    {
        cout << "分詞失敗!!" << endl;
    }
} 


2.   已知有隨機生成1和7之間數的函式,設計生成1到10之間隨機數的函式

題目:

已知一個函式rand7()能夠生成1-7的隨機數,請給出一個函式,該函式能夠生成1-10的隨機數。

思路:

假如已知一個函式能夠生成1-49的隨機數,那麼如何以此生成1-10的隨機數呢?

解法:

該解法基於一種叫做拒絕取樣的方法。主要思想是隻要產生一個目標範圍內的隨機數,則直接返回。如果產生的隨機數不在目標範圍內,則丟棄該值,重新取樣。由於目標範圍內的數字被選中的概率相等,這樣一個均勻的分佈生成了。

顯然rand7至少需要執行2次,否則產生不了1-10的數字。通過執行rand7兩次,可以生成1-49的整數,

   1  2  3  4  5  6  7
1  1  2  3  4  5  6  7
2  8  9 10  1  2  3  4
3  5  6  7  8  9 10  1
4  2  3  4  5  6  7  8
5  9 10  1  2  3  4  5
6  6  7  8  9 10  *  *
7  *  *  *  *  *  *  *

由於49不是10的倍數,所以我們需要丟棄一些值,我們想要的數字範圍為1-40,不在此範圍則丟棄並重新取樣。

程式碼:

int rand10() {
  int row, col, idx;
  do {
    row = rand7();
    col = rand7();
    idx = col + (row-1)*7;
  } while (idx > 40);
  return 1 + (idx-1)%10;
}


由於row範圍為1-7,col範圍為1-7,這樣idx值範圍為1-49。大於40的值被丟棄,這樣剩下1-40範圍內的數字,通過取模返回。下面計算一下得到一個滿足1-40範圍的數需要進行取樣的次數的期望值:

E(# calls to rand7) = 2 * (40/49) +
                      4 * (9/49) * (40/49) +
                      6 * (9/49)2 * (40/49) +
                      ...

                      
                    =  2k * (9/49)k-1 * (40/49)
                      k=1

                    = (80/49) / (1 - 9/49)2
                    = 2.45

優化:

上面的方法大概需要2.45次呼叫rand7函式才能得到1個1-10範圍的數,下面可以進行再度優化。

對於大於40的數,我們不必馬上丟棄,可以對41-49的數減去40可得到1-9的隨機數,而rand7可生成1-7的隨機數,這樣可以生成1-63的隨機數。對於1-60我們可以直接返回,而61-63則丟棄,這樣需要丟棄的數只有3個,相比前面的9個,效率有所提高。而對於61-63的數,減去60後為1-3,rand7產生1-7,這樣可以再度利用產生1-21的數,對於1-20我們則直接返回,對於21則丟棄。這時,丟棄的數就只有1個了,優化又進一步。當然這裡面對rand7的呼叫次數也是增加了的。程式碼如下:

int rand10Imp() {
  int a, b, idx;
  while (true) {
    a = rand7();
    b = rand7();
    idx = b + (a-1)*7;
    if (idx <= 40)
      return 1 + (idx-1)%10;
    a = idx-40;
    b = rand7();
    // get uniform dist from 1 - 63
    idx = b + (a-1)*7;
    if (idx <= 60)
      return 1 + (idx-1)%10;
    a = idx-60;
    b = rand7();
    // get uniform dist from 1-21
    idx = b + (a-1)*7;
    if (idx <= 20)
      return 1 + (idx-1)%10;
  }
}

下面計算下優化後方法的呼叫rand7函式的期望次數:

E(# calls to rand7) = 2 * (40/49) +
                      3 * (9/49) * (60/63) +
                      4 * (9/49) * (3/63) * (20/21) + 

                      (9/49) * (3/63) * (1/21) *
                      [ 6 * (40/49) +
                        7 * (9/49) * (60/63) +
                        8 * (9/49) * (3/63) * (20/21) ] +

                      ((9/49) * (3/63) * (1/21))2 *
                      [ 10 * (40/49) +
                        11 * (9/49) * (60/63) +
                        12 * (9/49) * (3/63) * (20/21) ] +
                      ...

                    = 2.2123

這裡期望次數為2.21,比起未優化的2.45次減少了大概10%。