微軟經典面試100題系列(部分)

Dave888Zhou發表於2014-07-10

    本文整理自:http://blog.csdn.net/v_july_v/article/details/6543438

    1. 把二元查詢樹轉變成排序的雙向連結串列
    題目:
輸入一棵二元查詢樹,將該轉換成個排 序的雙向連結串列。
要求不能建立任何新的結點,只調整指標向。
  10
/        \
6      14
/  \    /    \
4 8 12  16
    轉換成雙向連結串列
    4=6=8=10=12=14=16
    首先我們定義的二元查詢樹節點的資料結構如下:

struct BSTreeNode
{
  int m_nValue; // value of node
  BSTreeNode *m_pLeft; // left child of node
  BSTreeNode *m_pRight; // right child of node
};
    解答:

    這是一個可以使用遞迴的傳統問題。顯然可以使用中序遍歷(左-根-右)。按照這個方式遍歷樹,比較小的結點先訪問。如果我們每訪問一個結點,假設之前訪問過的結點已經調整成一個排序雙向連結串列,我們再把調整當前結點的指標將其連結到連結串列的末尾。當所有結點都訪問過之後,整棵樹也就轉換成一個排序雙向連結串列了。

BSTreeNode *pHead=NULL;
BSTreeNode *pListIndex=NULL;

// 遍歷二元查詢樹 中序
void ergodicBSTree(BSTreeNode * pCurrent)
{
	if (NULL == pCurrent) {
		return;
	}
	if (NULL != pCurrent->m_pLeft) {
		ergodicBSTree(pCurrent->m_pLeft);
	}
	// 節點接到連結串列尾部
	convertToDoubleList(pCurrent);
	// 右子樹為空
	if (NULL != pCurrent->m_pRight) {
		ergodicBSTree(pCurrent->m_pRight);
	}
}

// 二叉樹轉換成list
void convertToDoubleList(BSTreeNode * pCurrent)
{
	pCurrent->m_pLeft = pListIndex;
	if (NULL != pListIndex) {
		pListIndex->m_pRight = pCurrent;
	} else {
		pHead = pCurrent;
	}
	pListIndex = pCurrent;
	cout<<pCurrent->m_nValue<<endl;
}

   2.設計包含min 函式的棧。定義棧的資料結構,要求新增一個min 函式,能夠得到棧的最小元素。要求函式min、push 以及pop 的時間複雜度都是O(1)。

ANSWER:

Stack is a LIFO data structure. When some element is popped from the stack, the status will recover to the original status as before that element was pushed. So we can recover the minimum element, too.

    #define STACK_LEN 50  
      
    typedef struct  
    {  
            int     val;  
            int     min;  
    } stack_item;  
      
    typedef struct  
    {  
            stack_item      data[STACK_LEN];  
            int             top;  
    } stack;  
      
    void push(stack *stk, int val)  
    {  
            stk->data[++stk->top].val = val;  
            if (stk->top > 0)  
            {  
                    if (val < stk->data[stk->top - 1].min)    
                    //如果當前push進的元素小於棧中最小元素  
                            stk->data[stk->top].min = val;   //把當前元素置為棧中最小元素  
                    else  
                    //否則,不更新  
                            stk->data[stk->top].min = stk->data[stk->top - 1].min;   
            }  
            else  
                    stk->data[stk->top].min = val;  
    }  
      
    int pop(stack *stk)  
    {  
            return stk->data[stk->top--].val;  
    }  
      
    int min(stack *stk)  
    {  
            return stk->data[stk->top].min;  
    }  
    3.求子陣列的最大和
題目:
輸入一個整形陣列,陣列裡有正數也有負數。
陣列中連續的一個或多個整陣列成一個子陣列,每個子陣列都有一個和。
求所有子陣列的和的最大值。要求時間複雜度為O(n)。
例如輸入的陣列為1, -2, 3, 10, -4, 7, 2, -5,和最大的子陣列為3, 10, -4, 7, 2,
因此輸出為該子陣列的和18。
ANSWER:
A traditional greedy approach.
Keep current sum, slide from left to right, when sum < 0, reset sum to 0.

int maxSubarray(int a[], int size) {
  if (size<=0) error(“error array size”);
  int sum = 0;
  int max = - (1 << 31);
  int cur = 0;
  while (cur < size) {
    sum += a[cur++];
    if (sum > max) {
      max = sum;
    } else if (sum < 0) {
      sum = 0;
    }
  }
  return max;
}
    4.在二元樹中找出和為某一值的所有路徑
題目:輸入一個整數和一棵二元樹。
從樹的根結點開始往下訪問一直到葉結點所經過的所有結點形成一條路徑。
列印出和與輸入整數相等的所有路徑。
例如輸入整數22 和如下二元樹
10
/ \
5 12
/ \
4 7
則列印出兩條路徑:10, 12 和10, 5, 7。
二元樹節點的資料結構定義為:
struct BinaryTreeNode // a node in the binary tree
{
int m_nValue; // value of node
BinaryTreeNode *m_pLeft; // left child of node
BinaryTreeNode *m_pRight; // right child of node
};
ANSWER:
對樹進行深度優先遍歷,Use backtracking and recurison. We need a stack to help backtracking the path.

struct TreeNode {
  int data;
  TreeNode * left;
  TreeNode * right;
};

void printPaths(TreeNode * root, int sum) {
  int path[MAX_HEIGHT];
  helper(root, sum, path, 0);
}

void helper(TreeNode * root, int sum, int path[], int top) {
  path[top++] = root.data;
  sum -= root.data;
  if (root->left == NULL && root->right==NULL) {
    if (sum == 0) printPath(path, top);
  } else {
    if (root->left != NULL) helper(root->left, sum, path, top);
    if (root->right!=NULL) helper(root->right, sum, path, top);
  }
  top --;
  sum -= root.data;
}
    5.查詢最小的k 個元素
題目:輸入n 個整數,輸出其中最小的k 個。
例如輸入1,2,3,4,5,6,7 和8 這8 個數字,則最小的4 個數字為1,2,3 和4。
ANSWER:
This is a very traditional question...
O(nlogn): cat I_FILE | sort -n | head -n K
O(kn): do insertion sort until k elements are retrieved.
O(n+klogn): Take O(n) time to bottom-up build a min-heap. Then sift-down k-1 times.

So traditional that I don’t want to write the codes...
Only gives the siftup and siftdown function.

    參考我的文章“堆結構的運用”:http://blog.csdn.net/zhoudaxia/article/details/5669914
    插入排序參考我的文章“排序演算法的實現”:http://blog.csdn.net/zhoudaxia/article/details/4586285
    第6 題
騰訊面試題:
給你10 分鐘時間,根據上排給出十個數,在其下排填出對應的十個數
要求下排每個數都是先前上排那十個數在下排出現的次數。
上排的十個數如下:
【0,1,2,3,4,5,6,7,8,9】
舉一個例子,
數值: 0,1,2,3,4,5,6,7,8,9
分配: 6,2,1,0,0,0,1,0,0,0
0 在下排出現了6 次,1 在下排出現了2 次,
2 在下排出現了1 次,3 在下排出現了0 次....
以此類推..

    說實話,這題除了一次次迭代嘗試外,我沒想到什麼好的處理辦法。假設上排的陣列為a,下排對應的陣列為b,元素個數為n,按照題目的意思可以得到以下公式:
b1+b2+...+bn=na1*b1+a2*b2+...+an*bn=nb
中的元素來源於a這種一次多項表示式的求解我不會。

我覺得這題比實際上還要複雜,以下情況是必須要考慮的:
1、上排的資料元素並不一定是排序的。
2、上排的資料元素並不一定就有0,可能還有負數。
3、上排的資料元素可能會有重複的。
4、未必有對應的下排資料。

#include <iostream.h>
#define len 10

class NumberTB 
{ 
private:
    int top[len]; 
    int bottom[len];
    bool success;
public:
    NumberTB();
    int* getBottom();
    void setNextBottom();
    int getFrequecy(int num);
};

NumberTB::NumberTB() 
{   
    success = false; 
    //format top  
    for(int i=0;i<len;i++) 
    { 
        top[i] = i;         
    }         
}


int* NumberTB::getBottom()
{ 
    int i = 0;     
    while(!success) 
    { 
        i++; 
        setNextBottom(); 
    }         
    return bottom; 
} 

//set next bottom  
void NumberTB::setNextBottom() 
{ 
    bool reB = true; 
  
    for(int i=0;i<len;i++) 
    { 
        int frequecy = getFrequecy(i); 
      
        if(bottom[i] != frequecy) 
        { 
            bottom[i] = frequecy; 
            reB = false; 
        } 
    } 
    success = reB; 
} 

//get frequency in bottom  
int NumberTB::getFrequecy(int num)   //此處的num即指上排的數 i
{ 
    int count = 0; 
  
    for(int i=0;i<len;i++) 
    { 
        if(bottom[i] == num) 
            count++; 
    } 
    return count;    //cout即對應 frequecy
}

int main()
{  
    NumberTB nTB;
    int* result= nTB.getBottom(); 

    for(int i=0;i<len;i++)
    { 
        cout<<*result++<<endl; 
    } 
    return 0;
} 

    除了上面提到的,就樓主的程式而言,個人覺得有以下幾個改進建議:
1、類中success是個多餘的東西,如果設定這麼一個成員變數,就不應該在函式setNextBottom中再無謂多一個reB變數。
2、由於未必有解,getBottom可能限於死迴圈。
3、getBottom中變數i完全是多餘的。
4、getFrequecy中判斷有些是多餘的,可以考慮把我上面提到的公司考慮進去。
等有時間了,我再好好考慮如何寫個比較好的程式。

    第7 題
微軟亞院之程式設計判斷倆個連結串列是否相交。給出倆個單向連結串列的頭指標,比如h1,h2,判斷這倆個連結串列是否相交。為了簡化問題,我們假設倆個連結串列均不帶環。
問題擴充套件:
1.如果連結串列可能有環列?
2.如果需要求出倆個連結串列相交的第一個節點列?

ANSWER:

該問題的分析可參考本部落格的文章“判斷單連結串列是否存在環以及兩個連結串列是否相交 ”:http://blog.csdn.net/zhoudaxia/article/details/8884402

struct Node {
  int data;
  int Node *next;
};
// if there is no cycle.
int isJoinedSimple(Node * h1, Node * h2) {
  while (h1->next != NULL) {
    h1 = h1->next;
  }
  while (h2->next != NULL) {
    h2 = h2-> next;
  }
  return h1 == h2;
}

// if there could exist cycle
int isJoined(Node *h1, Node * h2) {
  Node* cylic1 = testCylic(h1);
  Node* cylic2 = testCylic(h2);
  if (cylic1+cylic2==0) return isJoinedSimple(h1, h2);
  if (cylic1==0 && cylic2!=0 || cylic1!=0 &&cylic2==0) return 0;
  Node *p = cylic1;
  while (1) {
    if (p==cylic2 || p->next == cylic2) return 1;
    p=p->next->next;
    cylic1 = cylic1->next;
    if (p==cylic1) return 0;
  }
}

Node* testCylic(Node * h1) {
  Node * p1 = h1, *p2 = h1;
  while (p2!=NULL && p2->next!=NULL) {
    p1 = p1->next;
    p2 = p2->next->next;
    if (p1 == p2) {
      return p1;
    }
  }
  return NULL;
}
   第8 題
此貼選一些比較怪的題,由於其中題目本身與演算法關係不大,僅考考思維。特此並作一題。
★有兩個房間,一間房裡有三盞燈,另一間房有控制著三盞燈的三個開關,這兩個房間是分割開的,從一間裡不能看到另一間的情況。現在要求受訓者分別進這兩房間一次,然後判斷出這三盞燈分別是由哪個開關控制的。有什麼辦法呢?
ANSWER:
Skip.
★你讓一些人為你工作了七天,你要用一根金條作為報酬。金條被分成七小塊,每天給出一塊。如果你只能將金條切割兩次,你怎樣分給這些工人?
ANSWER:
1+2+4;
★用一種演算法來顛倒一個連結表的順序。現在在不用遞迴式的情況下做一遍。
ANSWER:

Node * reverse(Node * head) {
  if (head == NULL) return head;
  if (head->next == NULL) return head;
  Node * ph = reverse(head->next);
  head->next->next = head;
  head->next = NULL;
  return ph;
}
Node * reverseNonrecurisve(Node * head) {
  if (head == NULL) return head;
  Node * p = head;
  Node * previous = NULL;
  while (p->next != NULL) {
    p->next = previous;
    previous = p;
    p = p->next;
  }
  p->next = previous;
  return p;
}
★用一種演算法在一個迴圈的連結表裡插入一個節點,但不得穿越連結表。
ANSWER:
I don’t understand what is “Chuanyue”.
★用一種演算法整理一個陣列。你為什麼選擇這種方法?
ANSWER:
What is “Zhengli?”
★用一種演算法使通用字串相匹配。
ANSWER:
What is “Tongyongzifuchuan”... a string with “*” and “?”? If so, here is the code.

int match(char * str, char * ptn) {
  if (*ptn == ‘\0’) return 1;
  if (*ptn == ‘*’) {
    do {
      if (match(str++, ptn+1)) return 1;
    } while (*str != ‘\0’);
    return 0;
  }
  if (*str == ‘\0’) return 0;
  if (*str == *ptn || *ptn == ‘?’) {
    return match(str+1, ptn+1);
  }
  return 0;
}
★顛倒一個字串。優化速度。優化空間。
void reverse(char *str) {
  reverseFixlen(str, strlen(str));
}
void reverseFixlen(char *str, int n) {
  char* p = str+n-1;
  while (str < p) {
    char c = *str;
    *str = *p; *p=c;
  }
}

★顛倒一個句子中的詞的順序,比如將“我叫克麗絲”轉換為“克麗絲叫我”,
實現速度最快,移動最少。
ANSWER:
Reverse the whole string, then reverse each word. Using the reverseFixlen() above.

void reverseWordsInSentence(char * sen) {
  int len = strlen(sen);
  reverseFixlen(sen, len);
  char * p = str;
  while (*p!=’\0’) {
    while (*p == ‘ ‘ && *p!=’\0’) p++;
    str = p;
    while (p!= ‘ ‘ && *p!=’\0’) p++;
    reverseFixlen(str, p-str);
  }
}

★找到一個子字串。優化速度。優化空間。
ANSWER:
KMP? BM? Sunday? Using BM or sunday, if it’s ASCII string, then it’s easy to fast access the auxiliary array. Otherwise an hashmap or bst may be needed. Lets assume it’s an ASCII string.

int bm_strstr(char *str, char *sub) {
  int len = strlen(sub);
  int i;
  int aux[256];
  memset(aux, sizeof(int), 256, len+1);
  for (i=0; i<len; i++) {
    aux[sub[i] = len - i;
  }
  int n = strlen(str);
  i=len-1;
  while (i<n) {
    int j=i, k=len-1;
    while (k>=0 && str[j--] == sub[k--])
      ;
    if (k<0) return j+1;
    if (i+1<n)
      i+=aux[str[i+1];
    else
      return -1;
  }
}
However, this algorithm, as well as BM, KMP algorithms use O(|sub|) space. If this is not acceptable, Rabin-carp algorithm can do it. Using hashing to fast filter out most false matchings.

#define HBASE 127
int rc_strstr(char * str, char * sub) {
  int dest= 0;
  char * p = sub;
  int len = 0;
  int TO_REDUCE = 1;
  while (*p!=’\0’) {
    dest = HBASE * dest + (int)(*p);
    TO_REDUCE *= HBASE;
    len ++;
  }
  int hash = 0;
  p = str;
  int i=0;
  while (*p != ‘\0’) {
    if (i++<len) hash = HBASE * dest + (int)(*p);
    else hash = (hash - (TO_REDUCE * (int)(*(p-len))))*HBASE + (int)(*p);
    if (hash == dest && i>=len && strncmp(sub, p-len+1, len) == 0) return i-len;
    p++;
  }
  return -1;
}
★比較兩個字串,用O(n)時間和恆量空間。
ANSWER:
What is “comparing two strings”? Just normal string comparison? The natural way use O(n) time and O(1) space.
int strcmp(char * p1, char * p2) {
  while (*p1 != ‘\0’ && *p2 != ‘\0’ && *p1 == *p2) {
    p1++, p2++;
  }
  if (*p1 == ‘\0’ && *p2 == ‘\0’) return 0;
  if (*p1 == ‘\0’) return -1;
  if (*p2 == ‘\0’) return 1;
  return (*p1 - *p2); // it can be negotiated whether the above 3 if’s are necessary, I don’t like to omit them.
}
★ 假設你有一個用1001 個整陣列成的陣列,這些整數是任意排列的,但是你知道所有的整數都在1 到1000(包括1000)之間。此外,除一個數字出現兩次外,其他所有數字只出現一次。假設你只能對這個陣列做一次處理,用一種演算法找出重複的那個數 字。如果你在運算中使用了輔助的儲存方式,那麼你能找到不用這種方式的演算法嗎?
ANSWER:
方法1:將資料組中的所有元素相加,然後減去1+2+3+...+1000=1000*1001/2;
方法2:使用性質A XOR A XOR B = B。可將這1001個整數與1到1000中的所有數異或,最後必得到這個出現兩次的數。

int findX(int a[]) {
  int k = a[0];
  for (int i=1; i<=1000;i++)
    k ~= a[i]~i;
  }
  return k;
}
★不用乘法或加法增加8 倍。現在用同樣的方法增加7 倍。
ANSWER:
n<<3;
(n<<3)-n;

    第9 題
判斷整數序列是不是二元查詢樹的後序遍歷結果
題目:輸入一個整數陣列,判斷該陣列是不是某二元查詢樹的後序遍歷的結果。
如果是返回true,否則返回false。
例如輸入5、7、6、9、11、10、8,由於這一整數序列是如下樹的後序遍歷結果:
8
/ \
6 10
/ \ / \
5 7 9 11
因此返回true。
如果輸入7、4、6、5,沒有哪棵樹的後序遍歷的結果是這個序列,因此返回false。
ANSWER:

傳統問題。根據遍歷結果(後序/中序/前序)來構造二叉查詢樹,涉及二叉樹遞迴是首選。對後序遍歷,最後一個元素就是樹的根,然後通過根把陣列分割成左子樹和右子樹兩部分,分割點在陣列第一個大於根的元素處(因為左子樹所有元素小於根,右子樹所有元素大於根),然後對左右子樹進行遞迴遍歷。其實,就是一個後序遍歷二叉樹的演算法。

bool verifySquenceOfBST(int squence[], int length)
{
      if(squence == NULL || length <= 0)
            return false;

      // root of a BST is at the end of post order traversal squence
      int root = squence[length - 1];

      // the nodes in left sub-tree are less than the root
      int i = 0;
      for(; i < length - 1; ++ i)
      {
            if(squence[i] > root)
                  break;
      }

      // the nodes in the right sub-tree are greater than the root
      int j = i;
      for(; j < length - 1; ++ j)
      {
            if(squence[j] < root)
                  return false;
      }

      // verify whether the left sub-tree is a BST
      bool left = true;
      if(i > 0)
            left = verifySquenceOfBST(squence, i);

      // verify whether the right sub-tree is a BST
      bool right = true;
      if(i < length - 1)
            right = verifySquenceOfBST(squence + i, length - i - 1);

      return (left && right);
}


    第10 題
翻轉句子中單詞的順序。
題目:輸入一個英文句子,翻轉句子中單詞的順序,但單詞內字元的順序不變。
句子中單詞以空格符隔開。為簡單起見,標點符號和普通字母一樣處理。
例如輸入“I am a student.”,則輸出“student. a am I”。

ANSWER:

可以利用堆疊後進先出的特性,把輸入先push到堆疊中,然後再pop出來,即反序。

#include<iostream>
#include<string>
using namespace std;

class ReverseWords{
public:
    ReverseWords(string* wo):words(wo){}
    void reverse_()
    {
        int length=words->size();
        int begin=-1,end=-1;
        for(int i=0;i<length;++i){
            if(begin==-1&&words->at(i)==' ')
                continue;
            if(begin==-1)
            {
                begin=i;
                continue;
            }
            if(words->at(i)==' ')
                end=i-1;
            else if(i==length-1)
                end=i;
            else
        continue;
            reverse__(begin,end);    //1.字母翻轉
            begin=-1,end=-1;          
        }
        reverse__(0,length-1);       //2.單詞翻轉
    }

private:
    void reverse__(int begin,int end)   //
    {
        while(begin<end)              
    {
            char t=words->at(begin);
            words->at(begin)=words->at(end);
            words->at(end)=t;
            ++begin;
            --end;
        }
    }
    string* words;
};


int main(){
    string s="I  am a student.";
    ReverseWords r(&s);
    r.reverse_();
    cout<<s<<endl;
  
    return 0;
}

執行結果:
student. a am I

    第11題

    求二叉樹中節點的最大距離...
如果我們把二叉樹看成一個圖,父子節點之間的連線看成是雙向的,我們姑且定義"距離"為兩節點之間邊的個數。寫一個程式,求一棵二叉樹中相距最遠的兩個節點之間的距離。
ANSWER:
This is interesting... Also recursively, the longest distance between two nodes must be either from root to one leaf, or between two leafs. For the former case, it’s the tree height. For the latter case, it should be the sum of the heights of left and right subtrees of the two leaves’ most least ancestor.
The first case is also the sum the heights of subtrees, just the height + 0.

int maxDistance(Node * root) {
  int depth;
  return helper(root, depth);
}
int helper(Node * root, int &depth) {
  if (root == NULL) {
    depth = 0; return 0;
  }
  int ld, rd;
  int maxleft = helper(root->left, ld);
  int maxright = helper(root->right, rd);
  depth = max(ld, rd)+1;
  return max(maxleft, max(maxright, ld+rd));
}
    第12 題
題目:求1+2+…+n,要求不能使用乘除法、for、while、if、else、switch、case等關鍵字以及條件判斷語句(A?B:C)。

方案1:
1+..+n=n*(n+1)/2=(n^2+n)/2
it is easy to get x/2, so the problem is to get n^2
though no if/else is allowed, we can easilly go around using short-pass. using macro to make it fancier:

#define T(X, Y, i) (Y & (1<<i)) && X+=(Y<<i)

int foo(int n){
  int r=n;
  T(r, n, 0); T(r, n,1); T(r, n, 2); … T(r, n, 31);
  return r >> 1;
}
方案2:

迴圈只是讓相同的程式碼執行n遍而已,我們完全可以不用for和while達到這個效果。比如定義一個類,我們new一個含有n個這種型別元素的陣列,那麼該類的建構函式將確定會被呼叫n次。我們可以將需要執行的程式碼放到建構函式裡。

#include <iostream.h>

class Temp
{
public:
      Temp()
      {
          ++N;
          Sum += N;
      }
      static void Reset() { N = 0; Sum = 0; }
      static int GetSum() { return Sum; }

private:
      static int N;
      static int Sum;
};

int Temp::N = 0;
int Temp::Sum = 0;

int solution1_Sum(int n)
{
      Temp::Reset();

      Temp *a = new Temp[n];   //就是這個意思,new出n個陣列。
       delete []a;
      a = 0;

      return Temp::GetSum();
}

int main()
{
    cout<<solution1_Sum(100)<<endl;
    return 0;
}
執行結果:
5050

方案3:

用遞迴,結合C++的虛擬函式呼叫來實現遞迴終止。既然不能判斷是不是應該終止遞迴,我們不妨定義兩個函式。一個函式充當遞迴函式的角色,另一個函式處理終止遞迴的情況,我們需要做的就是在兩個函式裡二選一。從二選一我們很自然的想到布林變數,比如ture/(1)的時候呼叫第一個函式,false/(0)的時候呼叫第二個函式。那現在的問題是如和把數值變數n轉換成布林值。如果對n連續做兩次反運算,即!!n,那麼非零的n轉換為true,0轉換為false。

#include <iostream.h>

class A;
A* Array[2];

class A
{
public:
    virtual int Sum (int n) { return 0; }
};

class B: public A
{
public:
    virtual int Sum (int n) { return Array[!!n]->Sum(n-1)+n; }
};

int solution2_Sum(int n)
{
    A a;
    B b;
    Array[0] = &a;
    Array[1] = &b;
   
    int value = Array[1]->Sum(n); 
    //利用虛擬函式的特性,當Array[1]為0時,即Array[0] = &a; 執行A::Sum,
    //當Array[1]不為0時,              即Array[1] = &b; 執行B::Sum。

    return value;
}

int main()
{
    cout<<solution2_Sum(100)<<endl;
    return 0;
}

方案4:

剛看到這個題目,我的第一想法就是多半用遞規解決,接下來難點就是遞規如何結束。題目的要求是比較苛刻的,樓主答案裡面用的兩種方法確實挺巧妙的,但我不會C++,因此只能另外想辦法。下面是我寫的程式碼:

int sum(int n)
{
        int val = 0;

        n > 0 && (val = n + sum(n - 1));

        return val;
}
雖然功能是實現了,但這個程式碼編譯時是有警告的,當然這個警告不重要。但我總覺得有更加合適的方法。

方案5:

模板超程式設計,最快捷的計算方式,編譯期完成計算。

#include <iostream>
using namespace std;

template<int N>
struct CalCls
{
    enum {sum = CalCls<N-1>::sum + N};
};

//模板特化,用於終止遞迴
template<>
struct CalCls<0>
{   
    enum {sum = 0};
};

int main()
{
    cout<<"1+2+3+...+100 = "<<CalCls<100>::sum<<endl;
    return 0;
}
    第13 題:
題目:輸入一個單向連結串列,輸出該連結串列中倒數第k 個結點。連結串列的倒數第0 個結點為連結串列的尾指標。
連結串列結點定義如下:
struct ListNode
{
int m_nKey;
ListNode* m_pNext;
};
答案:

思路1:遍歷一次記錄連結串列長度n,然後重新再遍歷n-k步。

思路2:稍微有點經驗的同志都會想到一種典型的套路。構造兩個步長差為k的指標,然後同時移動。設定兩個指標p1,p2,首先p1和p2都指向head,p2向前走k步,這樣p1和p2之間就間隔k個節點,然後p1和p2同時移動,p2到尾部時,p1到達目標節點。

兩種思路的時間複雜度完全相等。

Node * lastK(Node * head, int k) {
  if (k<0) error(“k < 0”);
  Node *p=head, *pk=head;
  for (;k>0;k--) {
    if (pk->next!=NULL) pk = pk->next;
    else return NULL;
  }
  while (pk->next!=NULL) {
    p=p->next, pk=pk->next;
  }
  return p;
}
    第14 題
題目:輸入一個已經按升序排序過的陣列和一個數字,在陣列中查詢兩個數,使得它們的和正好是輸入的那個數字。要求時間複雜度是O(n)。如果有多對數字的和等於輸入的數字,輸出任意一對即可。
例如輸入陣列1、2、4、7、11、15 和數字15。由於4+11=15,因此輸出4 和11。
ANSWER:
Use two cursors. One at front and the other at the end. Keep track of the sum by moving the cursors.

void find2Number(int a[], int n, int dest) {
  int *f = a, *e=a+n-1;
  int sum = *f + *e;
  while (sum != dest && f < e) {
    if (sum < dest) sum = *(++f);
    else sum = *(--e);
  }
  if (sum == dest) printf(“%d, %d\n”, *f, *e);
}
    第15 題:對樹做映象翻轉
題目:輸入一顆二元查詢樹,將該樹轉換為它的映象,即在轉換後的二元查詢樹中,左子樹的結點都大於右子樹的結點。用遞迴和迴圈兩種方法完成樹的映象轉換。
例如輸入:
8
/ \
6 10
/\ /\
5 7 9 11
輸出:
8
/ \
10 6
/\ /\
11 9 7 5
定義二元查詢樹的結點為:
struct BSTreeNode // a node in the binary search tree (BST)
{
int m_nValue; // value of node
BSTreeNode *m_pLeft; // left child of node
BSTreeNode *m_pRight; // right child of node
};
ANSWER:
    遞迴實現:這是遞迴的基本應用。就是遞迴翻轉樹,有子樹則遞迴翻轉子樹。
    非遞迴實現:由於遞迴的本質是編譯器生成了一個函式呼叫的棧,因此用迴圈來完成同樣任務時最簡單的辦法就是用一個輔助棧來模擬遞迴。首先我們把樹的頭結點放入棧中。在迴圈中,只要棧不為空,彈出棧的棧頂結點,交換它的左右子樹。如果它有左子樹,把它的左子樹壓入棧中;如果它有右子樹,把它的右子樹壓入棧中。這樣在下次迴圈中就能交換它兒子結點的左右子樹了。

void swap(Node ** l, Node ** r) {
  Node * p = *l;
  *l = *r;
  *r = p;
}

void mirror(Node * root) {
  if (root == NULL) return;
  swap(&(root->left), &(root->right));
  mirror(root->left);
  mirror(root->right);
}

void mirrorIteratively(Node * root) {
  if (root == NULL) return;
  stack<Node*> buf;
  buf.push(root);
  while (!stack.empty()) {
    Node * n = stack.pop();
    swap(&(root->left), &(root->right));
    if (root->left != NULL) buf.push(root->left);
    if (root->right != NULL) buf.push(root->right);
  }
}
    第16 題:
題目(微軟):
輸入一顆二元樹,從上往下按層列印樹的每個結點,同一層中按照從左往右的順序列印。
例如輸入
7
8
/ \
6 10
/ \ / \
5 7 9 11
輸出8 6 10 5 7 9 11。
ANSWER:

利用佇列做層次遍歷,樹的層次遍歷實際上是廣度優先遍歷。

由於STL已經為我們實現了一個很好的deque(兩端都可以進出的佇列),我們只需要拿過來用就可以了。

我們知道樹是圖的一種特殊退化形式。同時如果對圖的深度優先遍歷和廣度優先遍歷有比較深刻的理解,將不難看出這種遍歷方式實際上是一種廣度優先遍歷。因此這道題的本質是在二元樹上實現廣度優先遍歷。

#include <deque>
#include <iostream>
using namespace std;

struct BTreeNode // a node in the binary tree
{
      int         m_nValue; // value of node
      BTreeNode  *m_pLeft;  // left child of node
      BTreeNode  *m_pRight; // right child of node
};
BTreeNode* pListIndex;
BTreeNode* pHead;

void PrintFromTopToBottom(BTreeNode *pTreeRoot)
{
      if(!pTreeRoot)
            return;

      // get a empty queue
      deque<BTreeNode *> dequeTreeNode;

      // insert the root at the tail of queue
      dequeTreeNode.push_back(pTreeRoot);

      while(dequeTreeNode.size())
      {
            // get a node from the head of queue
            BTreeNode *pNode = dequeTreeNode.front();
            dequeTreeNode.pop_front();

            // print the node
            cout << pNode->m_nValue << ' ';

            // print its left child sub-tree if it has
            if(pNode->m_pLeft)
                  dequeTreeNode.push_back(pNode->m_pLeft);
            // print its right child sub-tree if it has
            if(pNode->m_pRight)
                  dequeTreeNode.push_back(pNode->m_pRight);
      }
}

// 建立二元查詢樹
void addBTreeNode(BTreeNode * & pCurrent, int value)
{
    if (NULL == pCurrent)
    {
        BTreeNode * pBTree = new BTreeNode();
        pBTree->m_pLeft = NULL;
        pBTree->m_pRight = NULL;
        pBTree->m_nValue = value;
        pCurrent = pBTree;

    }
    else
    {
        if ((pCurrent->m_nValue) > value)
        {
            addBTreeNode(pCurrent->m_pLeft, value);
        }
        else if ((pCurrent->m_nValue) < value)
        {
            addBTreeNode(pCurrent->m_pRight, value);
        }
    }
}

int main()
{
    BTreeNode * pRoot = NULL;
    pListIndex = NULL;
    pHead = NULL;
    addBTreeNode(pRoot, 8);
    addBTreeNode(pRoot, 6);
    addBTreeNode(pRoot, 5);
    addBTreeNode(pRoot, 7);
    addBTreeNode(pRoot, 10);
    addBTreeNode(pRoot, 9);
    addBTreeNode(pRoot, 11);
    PrintFromTopToBottom(pRoot);
    return 0;
}
    第17 題:
題目:在一個字串中找到第一個只出現一次的字元。如輸入abaccdeff,則輸出b。
分析:這道題是2006 年google 的一道筆試題。

思路剖析:

    由於題目與字元出現的次數相關,我們可以統計每個字元在該字串中出現的次數。要達到這個目的,需要一個資料容器來存放每個字元的出現次數。在這個資料容器中可以根據字元來查詢它出現的次數,也就是說這個容器的作用是把一個字元對映成一個數字。在常用的資料容器中,雜湊表正是這個用途。由於本題的特殊性,我們只需要一個非常簡單的雜湊表就能滿足要求。
    由於字元(char)是一個長度為8的資料型別,因此總共有可能256 種可能。於是我們建立一個長度為256的陣列,每個字母根據其ASCII碼值作為陣列的下標對應陣列的對應項,而陣列中儲存的是每個字元對應的次數。這樣我們就建立了一個大小為256,以字元ASCII碼為鍵值的雜湊表。我們第一遍掃描這個陣列時,每碰到一個字元,在雜湊表中找到對應的項並把出現的次數增加一次。這樣在進行第二次掃描時,就能直接從雜湊表中得到每個字元出現的次數了。

#include <iostream.h>
#include <string.h>

char FirstNotRepeatingChar(char* pString)
{
      if(!pString)
            return 0;

      const int tableSize = 256;
      unsigned int hashTable[tableSize];
      //初始化hash表
      for(unsigned int i = 0; i < tableSize; ++ i)
            hashTable[i] = 0;

      char* pHashKey = pString;
      while(*(pHashKey) != '\0')
            hashTable[*(pHashKey++)] ++;

      pHashKey = pString;
      while(*pHashKey != '\0')
      {
            if(hashTable[*pHashKey] == 1)
                  return *pHashKey;

            pHashKey++;
      }

      return *pHashKey;
}

int main()
{
    cout<<"請輸入一串字元:"<<endl;
    char s[100];
    cin>>s;
    char* ps=s;
    cout<<FirstNotRepeatingChar(ps)<<endl;
    return 0;
}
    第18 題:
題目:n 個數字(0,1,…,n-1)形成一個圓圈,從數字0 開始,每次從這個圓圈中刪除第m 個數字(第一個為當前數字本身,第二個為當前數字的下一個數字)。當一個數字刪除後,從被刪除數字的下一個繼續刪除第m 個數字。求出在這個圓圈中剩下的最後一個數字。
July:我想,這個題目,不少人已經見識過了。

簡單的約瑟夫迴圈問題。先看這個題目的簡單變形。
n個人圍成一圈,順序排號。從第一個人開始報數(從1到3報數),凡報到3的人退出圈子,問最後留下的是原來第幾號的那個人?
ANSWER:

一般的解答:

#include <stdio.h>  
  
int main()  
{  
    int i,k,m,n,num[50],*p;  
    printf("input number of person:n=");  
    scanf("%d",&n);  
      
    printf("input number of the quit:m=");     
    scanf("%d",&m);                            
      
    p=num;  
    for(i=0;i<n;i++)  
        *(p+i)=i+1;    //給每個人編號  
    i=0;   //報數  
    k=0;   //此處為3  
      
    while(m<n-1)  
    {  
        if(*(p+i)!=0)  
            k++;  
        if(k==3)  
        {  
            *(p+i)=0;    //退出,對應的陣列元素置為0  
            k=0;  
            m++;  
        }  
        i++;  
        if(i==n)  
            i=0;  
    }  
    while(*p==0)  
        p++;  
    printf("The last one is NO.%d/n",*p);  
    return 0;  
}  
另一種更優雅的方案:

The keys are:
1) if we shift the ids by k, namely, start from k instead of 0, we should add the result by k%n
2) after the first round, we start from k+1 ( possibly % n) with n-1 elements, that is equal to an (n-1) problem while start from (k+1)th element instead of 0, so the answer is (f(n-1, m)+k+1)%n
3) k = m-1, so f(n,m)=(f(n-1,m)+m)%n.

finally, f(1, m) = 0;
Now this is a O(n) solution.

int joseph(int n, int m) {
  int fn=0;
  for (int i=2; i<=n; i++) {
    fn = (fn+m)%i; }
  return fn;
}
    第19 題:
題目:定義Fibonacci 數列如下:
/ 0 n=0
f(n)= 1 n=1
\ f(n-1)+f(n-2) n=2
輸入n,用最快的方法求該數列的第n 項。
分析:

在很多C 語言教科書中講到遞迴函式的時候,都會用Fibonacci 作為例子。因此很多程式設計師對這道題的遞迴解法非常熟悉,但這並不能說明遞迴解法最適合這道題目。更簡單的辦法是從下往上計算,首先根據f(0)和f(1)算出f(2),再根據f(1)和f(2)算出f(3)……,依此類推就可以算出第n項了。很容易理解,這種思路的時間複雜度是O(n)。其實,就是轉化為非遞迴程式,用遞推。

long long Fibonacci_Solution2(unsigned n)
{
      int result[2] = {0, 1};
      if(n < 2)
            return result[n];

      long long  fibNMinusOne = 1;
      long long  fibNMinusTwo = 0;
      long long  fibN = 0;
      for(unsigned int i = 2; i <= n; ++ i)
      {
            fibN = fibNMinusOne + fibNMinusTwo;

            fibNMinusTwo = fibNMinusOne;
            fibNMinusOne = fibN;
      }

       return fibN;
}

很可惜,這還不是最快的方法。還有一種方法,可達到時間複雜度為O(log n)。可以用矩陣連乘得到: a[2][2]={1,1,1,0} a連乘n次,得到的a[0][1]和a[1][0]就是第n項。因為是連乘,所有可以用快速連乘(秦九韶),複雜度就可以是logN了。

let A=

{1 1}
{1 0}
f(n) = A^(n-1)[0,0]

程式碼如下:

int f(int n) {
  int A[4] = {1,1,1,0};
  int result[4];
  power(A, n, result);
  return result[0];
}

void multiply(int[] A, int[] B, int _r) {
  _r[0] = A[0]*B[0] + A[1]*B[2];
  _r[1] = A[0]*B[1] + A[1]*B[3];
  _r[2] = A[2]*B[0] + A[3]*B[2];
  _r[3] = A[2]*B[1] + A[3]*B[3];
}

void power(int[] A, int n, int _r) {
  if (n==1) { memcpy(A, _r, 4*sizeof(int)); return; }
  int tmp[4];
  power(A, n>>1, _r);
  multiply(_r, _r, tmp);
  if (n & 1 == 1) {
    multiply(tmp, A, _r);
  } else {
    memcpy(_r, tmp, 4*sizeof(int));
  }
}
    第20題:
題目:輸入一個表示整數的字串,把該字串轉換成整數並輸出。例如輸入字串"345",則輸出整數345。

此題一點也不簡單。不信,你就先不看一下的程式碼,你自己先寫一份,然後再對比一下,便知道了。
1. 轉換的思路:每掃描到一個字元,我們把在之前得到的數字乘以10再加上當前字元表示的數字。這個思路用迴圈不難實現。
2. 由於整數可能不僅僅之含有數字,還有可能以'+'或者'-'開頭,表示整數的正負。如果第一個字元是'+'號,則不需要做任何操作;如果第一個字元是'-'號,則表明這個整數是個負數,在最後的時候我們要把得到的數值變成負數。
3. 接著我們試著處理非法輸入。由於輸入的是指標,在使用指標之前,我們要做的第一件是判斷這個指標是不是為空。如果試著去訪問空指標,將不可避免地導致程式崩潰。
4. 輸入的字串中可能含有不是數字的字元。每當碰到這些非法的字元,我們就沒有必要再繼續轉換。
5. 最後一個需要考慮的問題是溢位問題。由於輸入的數字是以字串的形式輸入,因此有可能輸入一個很大的數字轉換之後會超過能夠表示的最大的整數而溢位。

enum Status {kValid = 0, kInvalid};
int g_nStatus = kValid;

int StrToInt(const char* str)
{
      g_nStatus = kInvalid;
      long long num = 0;

      if(str != NULL)
      {
            const char* digit = str;

            // the first char in the string maybe '+' or '-'
            bool minus = false;
            if(*digit == '+')
                  digit ++;
            else if(*digit == '-')
            {
                  digit ++;
                  minus = true;
            }

            // the remaining chars in the string
            while(*digit != '\0')
            {
                  if(*digit >= '0' && *digit <= '9')
                  {
                        num = num * 10 + (*digit - '0');

                        // overflow
                        if(num > std::numeric_limits<int>::max())
                        {
                              num = 0;
                              break;
                        }

                        digit ++;
                  }
                  // if the char is not a digit, invalid input
                  else
                  {
                        num = 0;
                        break;
                  }
            }

            if(*digit == '\0')
            {
                  g_nStatus = kValid;
                  if(minus)
                        num = 0 - num;
            }
      }
      return static_cast<int>(num);
}
    第21 題
2010 年中興面試題
程式設計求解:

輸入兩個整數n 和m,從數列1,2,3.......n 中隨意取幾個數,使其和等於m,要求將其中所有的可能組合列出來。

答案:

這是一個組合產生問題。與第14題差不多。可以使用遞迴。

    // 遞迴方法  
    //這個,沒有任何問題  
    //from yansha  
    //July、updated。  
    #include<list>  
    #include<iostream>  
    using namespace std;  
      
    list<int>list1;  
    void find_factor(int sum, int n)   
    {  
        // 遞迴出口  
        if(n <= 0 || sum <= 0)  
            return;  
          
        // 輸出找到的結果  
        if(sum == n)  
        {  
            // 反轉list  
            list1.reverse();  
            for(list<int>::iterator iter = list1.begin(); iter != list1.end(); iter++)  
                cout << *iter << " + ";  
            cout << n << endl;  
            list1.reverse();      
        }  
          
        list1.push_front(n);      //典型的01揹包問題  
        find_factor(sum-n, n-1);   //放n,n-1個數填滿sum-n  
        list1.pop_front();  
        find_factor(sum, n-1);     //不放n,n-1個數填滿sum   
    }  
      
    int main()  
    {  
        int sum, n;  
        cout << "請輸入你要等於多少的數值sum:" << endl;  
        cin >> sum;  
        cout << "請輸入你要從1.....n數列中取值的n:" << endl;  
        cin >> n;  
        cout << "所有可能的序列,如下:" << endl;  
        find_factor(sum,n);  
        return 0;  
    }  
    第22 題:
有4 張紅色的牌和4 張藍色的牌,主持人先拿任意兩張,再分別在A、B、C 三人額頭上貼任意兩張牌,A、B、C 三人都可以看見其餘兩人額頭上的牌,看完後讓他們猜自己額頭上是什麼顏色的牌,A 說不知道,B 說不知道,C 說不知道,然後A 說知道了。
請教如何推理,A 是怎麼知道的。如果用程式,又怎麼實現呢?

4張r,4張b,有以下3種組合:rr bb rb
1. B,C全是一種顏色
B C A
bb.rr bb.rr
2. B C A
    bb rr bb/RR/BR,=>A:BR
     rr bb =>A:BR
3. B C A
    BR BB RR/BR, =>A:BR
    推出A:BR的原因:如果 A是RR,那麼當ABC都說不知道後,B最後應該知道自己是BR了。因為B不可能是RR或BB。
4. B C A
    BR BR BB/RR/BR
    推出A:BR的原因:
//i、 如果A是BB,那麼B=>BR/RR,如果B=>RR,那麼一開始,C就該知道自己是BR了(A倆藍,B倆紅)。(如果C,A倆藍,那麼B就一開始知道,如果C.B倆紅,那麼A一開始就知道,所以論證前頭,當B=>RR,那麼一開始,C就該知道自己是BR)。如果B=>BR,那麼同樣道理,C一開始也該知道自己是BR了。
//ii、 如果A是RR,類似分析。
//iii、最後,也還是推出=>A:BR
    至於程式,暫等高人。
    第23 題:
用最簡單,最快速的方法計算出下面這個圓形是否和正方形相交。"
3D 座標系原點(0.0,0.0,0.0)
圓形:
半徑r = 3.0
圓心o = (*.*, 0.0, *.*)
正方形:
4 個角座標;
1:(*.*, 0.0, *.*)
2:(*.*, 0.0, *.*)
3:(*.*, 0.0, *.*)
4:(*.*, 0.0, *.*)
ANSWER
Crap... I totally cannot understand this problem... Does the *.* represent any possible number?

    第24 題:
連結串列操作
(1).單連結串列就地逆置,
(2)合併連結串列

ANSWER
Reversing a linked list. Already done.
What do you mean by merge? Are the original lists sorted and need to be kept sorted? If not, are there any special requirements? I will only do the sorted merging.

就地逆轉:

ListNode* ReverseIteratively(ListNode* pHead)
{
      ListNode* pReversedHead = NULL;
      ListNode* pNode = pHead;
      ListNode* pPrev = NULL;
      while(pNode != NULL)         //pNode<=>m
      {
            ListNode* pNext = pNode->m_pNext;       //n儲存在pNext下

            //如果pNext指為空,則當前結點pNode設為頭。
            if(pNext == NULL)
                  pReversedHead = pNode;

            //reverse the linkage between nodes
            pNode->m_pNext = pPrev;

            //move forward on the the list
            pPrev = pNode;
            pNode = pNext;
      }
      return pReversedHead;
}
合併有序連結串列:

Node * merge(Node * h1, Node * h2) {
  if (h1 == NULL) return h2;
  if (h2 == NULL) return h1;
  Node * head;
  if (h1->data>h2->data) {  //頭結點指向小的節點
    head = h2; h2=h2->next;
  } else {
    head = h1; h1=h1->next;
  }
  Node * current = head;
  while (h1 != NULL && h2 != NULL) { //把小的節點先鏈入當前連結串列
    if (h1 == NULL || (h2!=NULL && h1->data>h2->data)) {
      current->next = h2; h2=h2->next; current = current->next;
    } else {
      current->next = h1; h1=h1->next; current = current->next;
    }
  }
  current->next = NULL;
  return head;
}
    第25 題:
寫一個函式,它的原形是int continumax(char *outputstr,char *intputstr)
功能:
在字串中找出連續最長的數字串,並把這個串的長度返回,
並把這個最長數字串付給其中一個函式引數outputstr 所指記憶體。
例如:"abcd12345ed125ss123456789"的首地址傳給intputstr 後,函式將返回9,
outputstr 所指的值為123456789
ANSWER:
這個相對比較簡單,跟在序列中求最小值差不多。用一個自動機掃描過去就OK了,開始狀態,字元狀態,數字狀態,結束狀態。

int continumax(char *outputstr, char *inputstr) {
  int len = 0;
  char * pstart = NULL;
  int max = 0;
  while (1) {
    if (*inputstr >= ‘0’ && *inputstr <=’9’) {
      len ++;
    } else {
      if (len > max) pstart = inputstr-len;
      len = 0;
    }
    if (*inputstr++==’\0’) break;
  }
  for (int i=0; i<len; i++)
    *outputstr++ = pstart++;
  *outputstr = ‘\0’;
  return max;
}
    26.左旋轉字串
題目:
定義字串的左旋轉操作:把字串前面的若干個字元移動到字串的尾部。
如把字串abcdef 左旋轉2 位得到字串cdefab。請實現字串左旋轉的函式。
要求時間對長度為n 的字串操作的複雜度為O(n),輔助記憶體為O(1)。
分析:

    如果不考慮時間和空間複雜度的限制,最簡單的方法莫過於把這道題看成是把字串分成前後兩部分,通過旋轉操作把這兩個部分交換位置。於是我們可以新開闢一塊長度為n+1的輔助空間,把原字串後半部分拷貝到新空間的前半部分,在把原字串的前半部分拷貝到新空間的後半部分。不難看出,這種思路的時間複雜度是O(n),需要的輔助空間也是O(n)。
    把字串看成有兩段組成的,記位XY。左旋轉相當於要把字串XY變成YX。我們先在字串上定義一種翻轉的操作,就是翻轉字串中字元的先後順序。把X翻轉後記為XT。顯然有(XT)T=X。我們首先對X和Y兩段分別進行翻轉操作,這樣就能得到XTYT。接著再對XTYT進行翻轉操作,得到(XTYT)T=(YT)T(XT)T=YX。正好是我們期待的結果。
    分析到這裡我們再回到原來的題目。我們要做的僅僅是把字串分成兩段,第一段為前面m個字元,其餘的字元分到第二段。再定義一個翻轉字串的函式,按照前面的步驟翻轉三次就行了。時間複雜度和空間複雜度都合乎要求。

#include "string.h"

// Move the first n chars in a string to its end
char* LeftRotateString(char* pStr, unsigned int n)
{
    if(pStr != NULL)
    {
        int nLength = static_cast<int>(strlen(pStr));
        if(nLength > 0 || n == 0 || n > nLength)
        {
            char* pFirstStart = pStr;
            char* pFirstEnd = pStr + n - 1;
            char* pSecondStart = pStr + n;
            char* pSecondEnd = pStr + nLength - 1;
           
            // reverse the first part of the string
            ReverseString(pFirstStart, pFirstEnd);
            // reverse the second part of the strint
            ReverseString(pSecondStart, pSecondEnd);
            // reverse the whole string
            ReverseString(pFirstStart, pSecondEnd);
        }
    }
   
    return pStr;
}

// Reverse the string between pStart and pEnd
void ReverseString(char* pStart, char* pEnd)
{
    if(pStart == NULL || pEnd == NULL)
    {
        while(pStart <= pEnd)
        {
            char temp = *pStart;
            *pStart = *pEnd;
            *pEnd = temp;
           
            pStart ++;
            pEnd --;
        }
    }
}
以abcdef為例
1. ab->ba
2. cdef->fedc
原字串變為bafedc
3. 最後整個翻轉一下得到cdefab
時間複雜度為O(n)

另外一種思路:
abc defghi,要abc移動至最後,可以是這樣的過程:abc defghi->def abcghi->def ghiabc
使用倆指標,p1指向ch[0],p2指向ch[m-1],p2每次移動m 的距離,p1 也跟著相應移動,
每次移動過後,交換。如上第一步,交換abc 和def ,就變成了 abcdef->defabc
第一步,
abc defghi->def abcghi
第二步,繼續交換,
def abcghi->def ghiabc
整個過程,看起來,就是abc 一步一步 向後移動
abc defghi
def abcghi
def ghi abc
//最後的 複雜度是O(m+n)
再舉一個例子,如果123 4567890要變成4567890 123:
123 4567890
1. 456 123 7890
2. 456789 123 0
3. 456789 12 0 3
4. 456789 1 0 23
5. 4567890 123 //最後三步,相當於0前移,p1已經不動。

    27.跳臺階問題
題目:一個臺階總共有n 級,如果一次可以跳1 級,也可以跳2 級。求總共有多少總跳法,並分析演算法的時間複雜度。
這道題最近經常出現,包括MicroStrategy 等比較重視演算法的公司,都曾先後選用過個這道題作為面試題或者筆試題。

分析:

首先我們考慮最簡單的情況。如果只有1級臺階,那顯然只有一種跳法。如果有2級臺階,那就有兩種跳的方法了:一種是分兩次跳,每次跳1級;另外一種就是一次跳2級。
現在我們再來討論一般情況。我們把n級臺階時的跳法看成是n的函式,記為f(n)。當n>2時,第一次跳的時候就有兩種不同的選擇:一是第一次只跳1級,此時跳法數目等於後面剩下的n-1級臺階的跳法數目,即為f(n-1);另外一種選擇是第一次跳2級,此時跳法數目等於後面剩下的n-2級臺階的跳法數目,即為f(n-2)。因此n級臺階時的不同跳法的總數f(n)=f(n-1)+(f-2)。
我們把上面的分析用一個公式總結如下:
/ 1,n=1
f(n)= 2, n=2
/ f(n-1)+(f-2),n>2
分析到這裡,相信很多人都能看出這就是我們熟悉的Fibonacci序列。

int jump_sum(int n)  //遞迴版本
{
    assert(n>0);
    if (n == 1 || n == 2) return n;
    return jump_sum(n-1)+jump_sum(n-2);
}

int jump_sum(int n) //迭代版本
{
    assert(n>0);
    if (n == 1 || n == 2) return n;

    int an, an_1=2, an_2=1;
    for (; n>=3; n--)
    {   
        an = an_2 + an_1;
        an_2 = an_1;
        an_1 = an;
    }
    return an;
}
    28.整數的二進位制表示中1 的個數
題目:輸入一個整數,求該整數的二進位制表達中有多少個1。
例如輸入10,由於其二進位制表示為1010,有兩個1,因此輸出2。
分析:
這是一道很基本的考查位運算的面試題。包括微軟在內的很多公司都曾採用過這道題。
方案1:使用x&(x-1)技巧,每一次都清除最右邊的1,直到x變成0。注意對x為負數此方法仍然適用。

Traditional question. Use the equation xxxxxx10000 & (xxxxxx10000-1) = xxxxxx00000
Note: for negative numbers, this also hold, even with 100000000 where the “-1” leading to an underflow.

int countOf1(int n) {
  int c=0;
  while (n!=0) {
    n=n & (n-1);
    c++;
  }
  return c;
}
方案2:

保持x不變,用一個數來探測x的每一位是否為1。這種方法比較通用。首先x和1做與運算,判斷i的最低位是不是為1。接著把1左移一位得到2,再和x做與運算,就能判斷x的次高位是不是1……,這樣反覆左移,每次都能判斷x的其中一位是不是1。

int NumberOf1_Solution2(int i)
{
      int count = 0;
      unsigned int flag = 1;
      while(flag)
      {
            if(i & flag)
                  count ++;

            flag = flag << 1;
      }

      return count;
}
可以看出方案1的時間複雜恰好為1的個數,方案2 的時間複雜度為int型別佔的位數,即32,方案1的時間複雜度更優越。

    29.棧的push、pop 序列
題目:輸入兩個整數序列。其中一個序列表示棧的push 順序,
判斷另一個序列有沒有可能是對應的pop 順序。
為了簡單起見,我們假設push 序列的任意兩個整數都是不相等的。
比如輸入的push 序列是1、2、3、4、5,那麼4、5、3、2、1 就有可能是一個pop 系列。
因為可以有如下的push 和pop 序列:
push 1,push 2,push 3,push 4,pop,push 5,pop,pop,pop,pop,
這樣得到的pop 序列就是4、5、3、2、1。
但序列4、3、5、1、2 就不可能是push 序列1、2、3、4、5 的pop 序列。

ANSWER
This seems interesting. However, a quite straightforward and promising way is to actually build the stack and check whether the pop action can be achieved.

如果我們希望pop的數字正好是棧頂數字,直接pop出棧即可;如果希望pop的數字目前不在棧頂,我們就到push序列中還沒有被push到棧裡的數字中去搜尋這個數字,並把在它之前的所有數字都push進棧。如果所有的數字都被push進棧仍然沒有找到這個數字,表明該序列不可能是一個pop序列。

int isPopSeries(int push[], int pop[], int n) {
  stack<int> helper;
  int i1=0, i2=0;
  while (i2 < n) {
    //pop的數字不在棧頂
    while (stack.empty() || stack.peek() != pop[i2])
      if (i1<n)  //繼續push
        stack.push(push[i1++]);
      else  //所有數字進棧後都沒找到,則不是一個pop序列
         return 0;
    //在棧頂,直接pop出棧
    while (!stack.empty() && stack.peek() == pop[i2]) {
        stack.pop(); i2++;
      }
  }
  return 1;
}
    30.在從1 到n 的正數中1 出現的次數
題目:輸入一個整數n,求從1 到n 這n 個整數的十進位制表示中1 出現的次數。
例如輸入12,從1 到12 這些整數中包含1 的數字有1,10,11 和12,1 一共出現了5 次。
分析:這是一道廣為流傳的google 面試題。

ANSWER
This is complicated... I hate it...
Suppose we have N=ABCDEFG.
if G<1, # of 1’s in the units digits is ABCDEF, else ABCDEF+1
if F<1, # of 1’s in the digit of tens is (ABCDE)*10, else if F==1: (ABCDE)*10+G+1, else (ABCDE+1)*10
if E<1, # of 1’s in 3rd digit is (ABCD)*100, else if E==1: (ABCD)*100+FG+1, else (ABCD+1)*100
… so on.
if A=1, # of 1 in this digit is BCDEFG+1, else it’s 1*1000000;
so to fast access the digits and helper numbers, we need to build the fast access table of prefixes and suffixes.

int countOf1s(int n) {
  int prefix[10], suffix[10], digits[10]; //10 is enough for 32bit integers
  int i=0;
  int base = 1;
  while (base < n) {
   suffix[i] = n % base;
   digit[i] = (n % (base * 10)) - suffix[i];
   prefix[i] = (n - suffix[i] - digit[i]*base)/10;
    i++, base*=10;
  }
  int count = 0;
  base = 1;
  for (int j=0; j<i; j++) {
    if (digit[j] < 1) count += prefix;
    else if (digit[j]==1) count += prefix + suffix + 1;
    else count += prefix+base;
    base *= 10;
  }
  return count;
}

方案2:
我們每次判斷整數的個位數字是不是1。如果這個數字大於10,除以10之後再判斷個位數字是不是1。基於這個思路,不難寫出如下的程式碼:

int NumberOf1(unsigned int n);

int NumberOf1BeforeBetween1AndN_Solution1(unsigned int n)
{
      int number = 0;

      // Find the number of 1 in each integer between 1 and n
      for(unsigned int i = 1; i <= n; ++ i)
            number += NumberOf1(i);

      return number;
}

int NumberOf1(unsigned int n)
{
      int number = 0;
      while(n)
      {
            if(n % 10 == 1)
                  number ++;

            n = n / 10;
      }

      return number;
}
這個思路比較簡單,易於理解。當然有一個非常明顯的缺點就是每個數字都要計算1在該數字中出現的次數,因此時間複雜度是O(n)。當輸入的n非常大的時候,需要大量的計算,運算效率很低。
    31.華為面試題:
一類似於蜂窩的結構的圖,進行搜尋最短路徑(要求5 分鐘)
ANSWER
Not clear problem. Skipped. Seems a Dijkstra could do.

    32題
有兩個序列a,b,大小都為n,序列元素的值任意整數,無序;
要求:通過交換a,b 中的元素,使[序列a 元素的和]與[序列b 元素的和]之間的差最小。
例如:
var a=[100,99,98,1,2, 3];
var b=[1, 2, 3, 4,5,40];

ANSWER
If only one swap can be taken, it is a O(n^2) searching problem, which can be reduced to O(nlogn) by sorting the arrays and doing binary search.
If any times of swaps can be performed, this is a double combinatorial problem.
In the book <<beauty of codes>>, a similar problem splits an array to halves as even as possible. It is possible to take binary search, when SUM of the array is not too high. Else this is a quite time consuming brute force problem. I cannot figure out a reasonable solution.

求解思路2:
當前陣列a和陣列b的和之差為
A = sum(a) - sum(b)

a的第i個元素和b的第j個元素交換後,a和b的和之差為
A' = sum(a) - a[i] + b[j] - (sum(b) - b[j] + a[i])
= sum(a) - sum(b) - 2 (a[i] - b[j])
= A - 2 (a[i] - b[j])

設x = a[i] - b[j]
|A| - |A'| = |A| - |A-2x|

假設A > 0,
當x 在 (0,A)之間時,做這樣的交換才能使得交換後的a和b的和之差變小,
x越接近A/2效果越好,
如果找不到在(0,A)之間的x,則當前的a和b就是答案。

所以演算法大概如下:
在a和b中尋找使得x在(0,A)之間並且最接近A/2的i和j,交換相應的i和j元素,
重新計算A後,重複前面的步驟直至找不到(0,A)之間的x為止。

求解思路3:

/////////////////////////////////////////
演算法
1. 將兩序列合併為一個序列,並排序,為序列Source
2. 拿出最大元素Big,次大的元素Small
3. 在餘下的序列S[:-2]進行平分,得到序列max,min
4. 將Small加到max序列,將Big加大min序列,重新計算新序列和,和大的為max,小的為min。
////////////////////////////////////////////////

def mean( sorted_list ):
    if not sorted_list:
        return (([],[]))
 
    big = sorted_list[-1]
    small = sorted_list[-2]
    big_list, small_list = mean(sorted_list[:-2])
 
    big_list.append(small)
    small_list.append(big)
 
    big_list_sum = sum(big_list)
    small_list_sum = sum(small_list)
 
    if big_list_sum > small_list_sum:
        return ( (big_list, small_list))
    else:
        return (( small_list, big_list))
 
tests = [   [1,2,3,4,5,6,700,800],
            [10001,10000,100,90,50,1],
            range(1, 11),
            [12312, 12311, 232, 210, 30, 29, 3, 2, 1, 1]
            ]
for l in tests:
    l.sort()
    print
    print "Source List:/t", l
    l1,l2 = mean(l)
    print "Result List:/t", l1, l2
    print "Distance:/t", abs(sum(l1)-sum(l2))
    print '-*'*40


輸出結果

Source List:    [1, 2, 3, 4, 5, 6, 700, 800]
Result List:    [1, 4, 5, 800] [2, 3, 6, 700]
Distance:       99
-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*

Source List:    [1, 50, 90, 100, 10000, 10001]
Result List:    [50, 90, 10000] [1, 100, 10001]
Distance:       38
-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*

Source List:    [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Result List:    [2, 3, 6, 7, 10] [1, 4, 5, 8, 9]
Distance:       1
-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*

Source List:    [1, 1, 2, 3, 29, 30, 210, 232, 12311, 12312]
Result List:    [1, 3, 29, 232, 12311] [1, 2, 30, 210, 12312]
Distance:       21
   

相關文章