微軟等面試100題(40-60)

本文轉自CSDN博客:http://blog.csdn.net/v_JULY_v/archive/2011/02/01/6171539.aspx

 

40、求固晶機的晶元查找程序
晶元盤由數目不詳的大小一樣的晶元組成,晶元並不一定全佈滿晶元盤,

照相機每次這能匹配一個晶元,如匹配過,則拾取該晶元,
若匹配不過,照相機則按測好的晶元間距移到下一個位置。
求遍歷晶元盤的算法 求思路。

 

關於第41題,請看以下網友的回覆:
xiuxianshen
感覺很簡單啊,你對應你的元件個數新建兩個相同維數的一維數組,
一組保存檢測的匹配情況,一組保存該元件的距離,二維數組也可以,
遍歷前先考慮數據參數就可以了。

 

kicming
難就難在元件的分佈情況是未知的 對機器來說 它是不知道邊界的 它自己不知道換行
所以很難規定換行的條件 比如從左向右找 找到某個地方發現沒有元件了
那是換行還是不換行呢

換行的話,右邊可能還有元件,不換行,可能當前已經到晶元盤邊界了 再往右找就是地板了..
所以想求一個“盲目”遍歷算法。

 


41.請修改append函數,利用這個函數實現:
兩個非降序鏈表的並集,1->2->3 和 2->3->5 併爲 1->2->3->5
另外只能輸出結果,不能修改兩個鏈表的數據。

 

此題,合併鏈表,要求將倆個非有序排列的鏈表,有順序的合併。
如下:
//引自一網友。
#include <stdio.h>
#include <malloc.h>

typedef struct lnode {
   
    int data;
    struct lnode *next;
}lnode,*linklist;

linklist creatlist(int m)//創建鏈表
{
   
    linklist p,l,s;
    int i;
    p=l=(linklist)malloc(sizeof(lnode));
    p->next=NULL;
    printf("請輸入鏈表中的一個數字:");
    scanf("%d",&p->data);
    for(i=2;i<=m;i++)
    {
        s=(linklist)malloc(sizeof(lnode));
        s->next = NULL;
        printf("請輸入第%d個數字",i);
        scanf("%d",&s->data);
        p->next=s;
        p=p->next;
    }
    printf("/n");
    return l;   
}
void print(linklist h)//打印鏈表
{
    linklist p=h->next;
    int t=1;
    printf("打印各個數字:/n");
    do
    {
        printf("請輸出第%d個數:",t);
        printf("%d/n",p->data);
        p=p->next;
        t++;
    }while(p);
}
linklist mergelist(void)//兩個鏈表合併
{
    int e,n;
    linklist pa,pb,pc,head;
    printf("請輸入第一個鏈表的長度:");
    scanf("%d",&e);
    pa=creatlist(e);
    printf("請輸入第二個鏈表的長度:");
    scanf("%d",&n);
    pb=creatlist(n);
    head=pc=(linklist)malloc(sizeof(lnode));
    pc->next=NULL;
    while(pa&&pb)
    {
        if(pa->data<=pb->data)
        {
            pc->next=pa;
            pc=pa;
            pa=pa->next;
        }
        else
        {
            pc->next=pb;
            pc=pb;
            pb=pb->next;
        }
    }
    pc->next=pa?pa:pb;
    return head;
}
void main()
{
    linklist head;
    head=mergelist();
    print(head);
}

///////////////////////////////////
請輸入第一個鏈表的長度:5
請輸入鏈表中的一個數字:3
請輸入第2個數字2
請輸入第3個數字1
請輸入第4個數字7
請輸入第5個數字9

請輸入第二個鏈表的長度:5
請輸入鏈表中的一個數字:6
請輸入第2個數字4
請輸入第3個數字5
請輸入第4個數字8
請輸入第5個數字7

打印各個數字:
請輸出第1個數:3
請輸出第2個數:2
請輸出第3個數:1
請輸出第4個數:6
請輸出第5個數:4
請輸出第6個數:5
請輸出第7個數:7
請輸出第8個數:8
請輸出第9個數:7
請輸出第10個數:9
Press any key to continue


//引用yangsen600。
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>

struct Node
{
    int num;
    Node * next;
};

Node * createTail()
{
    int x;
    Node *head = NULL, *p = NULL, *tail = NULL;
    puts("/nplease enter some digits(end of '.'):");
    while( 1 == scanf("%d",&x) )
    {
        p = (Node *)malloc(sizeof(Node));
        p->num = x;
        p->next = NULL;
        if( NULL == head )
        {
            tail = p;
            head = tail;
        }
        else
        {
            tail->next = p;
            tail = p;
        }
    }
    getchar();
    return head;
}

Node * CombinationNode(Node* head1, Node* head2)
{
    Node *head,*tail,*p = head1,*q = head2,*s;
   
    if( NULL == p )
        return q;
    if( NULL == q )
        return p;
   
    tail = p;
    if( p->num > q->num)       
        tail = q;
    head = tail;
   
    while( NULL != p && NULL != q )
    {
        if(p->num <= q->num )        
        //如果p所指元素<q所指元素,那麼把p所指元素,率先拉入合併後的鏈表中,
        //p賦給s,並從p的下一個元素p->next查找。
        //直到發現p所指 不再 < q,而是p > q了 即轉至下述代碼的else部分。
        {
            s = p;      
            p = p->next;
        }
        else
        {
            s = q;
            q = q->next;
        }
        tail->next = s;
        tail = s;
    }
   
    if( NULL == p )
        p = q;
    s = p;
    tail->next = s;
   
    return head;
}

void printHead(Node *head)
{
    if( NULL == head )
        return;
    printf("List: ");
    while(head)
    {
        printf("%d->",head->num);
        head = head->next;
    }
    puts("NUL");
}

void main( void )
{
    Node* head1,*head2,*head;
    head1 = createTail();
    printHead(head1);
   
    head2 = createTail();
    printHead(head2);
   
    head = CombinationNode(head1,head2);
    printHead(head);
}

//////////////////////////////////////////
please enter some digits(end of '.'):
3 2 1 7 9.
List: 3->2->1->7->9->NUL

please enter some digits(end of '.'):
6 4 5 8 7.
List: 6->4->5->8->7->NUL
List: 3->2->1->6->4->5->7->8->7->9->NUL
Press any key to continue
//與上述那段,輸出結果一致。

已知兩個鏈表head1 和head2 各自有序,請把它們合併成一個鏈表依然有序。
//非遞歸實現 鏈表合併排序:
Node * Merge(Node *head1 , Node *head2)
{
    if ( head1 == NULL)
        return head2 ;
    if ( head2 == NULL)
        return head1 ;
    Node *head = NULL ;
    Node *p1 = NULL;
    Node *p2 = NULL;
    if ( head1->data < head2->data )
    {
        head = head1 ;
        p1 = head1->next;
        p2 = head2 ;
    }
    else
    {
        head = head2 ;
        p2 = head2->next ;
        p1 = head1 ;
    }
    Node *pcurrent = head ;
    while ( p1 != NULL && p2 != NULL)
    {
        if ( p1->data <= p2->data )
        {
            pcurrent->next = p1 ;
            pcurrent = p1 ;
            p1 = p1->next ;
        }
        else
        {
            pcurrent->next = p2 ;
            pcurrent = p2 ;
            p2 = p2->next ;
        }
    }
    if ( p1 != NULL )
        pcurrent->next = p1 ;
    if ( p2 != NULL )
        pcurrent->next = p2 ;
    return head ;
}


//遞歸實現,
Node * MergeRecursive(Node *head1 , Node *head2)
{
  if ( head1 == NULL )
    return head2 ;
  if ( head2 == NULL)
    return head1 ;
  Node *head = NULL ;
  if ( head1->data < head2->data )
  {
    head = head1 ;
    head->next = MergeRecursive(head1->next,head2);
  }
  else
  {
    head = head2 ;
    head->next = MergeRecursive(head1,head2->next);
  }
  return head ;
}


不放比較一下,這倆段核心代碼,注意其區別:
Node * CombinationNode(Node* head1, Node* head2)
{
    Node *head,*tail,*p = head1,*q = head2,*s;
   
    if( NULL == p )
        return q;
    if( NULL == q )
        return p;
   
    tail = p;
    if( p->num > q->num)       
        tail = q;
    head = tail;
   
    while( NULL != p && NULL != q )
    {
        if(p->num <= q->num )        
        {
        s = p;           //3.4
            p = p->next;     //
        }
        else
        {
            s = q;
            q = q->next;
        }
        tail->next = s;
        tail = s;
    }
   
    if( NULL == p )
    p = q;
    s = p;
    tail->next = s;
   
    return head;
}

和這段:
linklist mergelist(void)//兩個鏈表合併
{
    int e,n;
    linklist pa,pb,pc,head;
    printf("請輸入第一個鏈表的長度:");
    scanf("%d",&e);
    pa=creatlist(e);
    printf("請輸入第二個鏈表的長度:");
    scanf("%d",&n);
    pb=creatlist(n);
    head=pc=(linklist)malloc(sizeof(lnode));   //1.這
    pc->next=NULL;      //2.這
    while(pa&&pb)
    {
        if(pa->data<=pb->data)
        {
            pc->next=pa;    //3.這
            pc=pa;
            pa=pa->next;     
        }
        else
        {
            pc->next=pb;    //4.這
            pc=pb;
            pb=pb->next;
        }
    }
    pc->next=pa?pa:pb;
    return head;
}


再比較下,這倆段:
linklist mergelist(void)//兩個鏈表合併
{
    int e,n;
    linklist pa,pb,pc,head;
    printf("請輸入第一個鏈表的長度:");
    scanf("%d",&e);
    pa=creatlist(e);
    printf("請輸入第二個鏈表的長度:");
    scanf("%d",&n);
    pb=creatlist(n);
    head=pc=(linklist)malloc(sizeof(lnode));
    pc->next=NULL;
    while(pa&&pb)
    {
        if(pa->data<=pb->data)
        {
            pc->next=pa;  //3
            pc=pa;        //1
            pa=pa->next;  //2
        }
        else
        {
            pc->next=pb;
            pc=pb;
            pb=pb->next;
        }
    }
    pc->next=pa?pa:pb;
    return head;
}


//遞歸實現,
Node * MergeRecursive(Node *head1 , Node *head2)
{
  if ( head1 == NULL )
    return head2 ;
  if ( head2 == NULL)
    return head1 ;
  Node *head = NULL ;
  if ( head1->data < head2->data )
  {
    head = head1 ;
    head->next = MergeRecursive(head1->next,head2);
  }
  else
  {
    head = head2 ;
    head->next = MergeRecursive(head1,head2->next);
  }
  return head ;
}

相當於,
if ( head1->data < head2->data )
  {
    head = head1 ;                                      //1.head=head1;
    head->next = MergeRecursive(head1->next,head2);     //2.head1=head1->next;
                                                        //3.head->next=head1
  }
  else
  {
    head = head2 ;
    head->next = MergeRecursive(head1,head2->next);
  }
  return head ;
聰明的你,相信,不要我過多解釋。:)。

 

 

第43題:
43.遞歸和非遞歸倆種方法實現二叉樹的前序遍歷。

咱們先來複習下,基礎知識。
因爲關於樹的遍歷,在此100題中已出現過太多次了。

二叉樹結點存儲的數據結構:
typedef char datatype;
typedef struct node
 {
   datatype data;
   struct node* lchild,*rchild;
 } bintnode;

typedef bintnode* bintree;
bintree root;

 

1.樹的前序遍歷即:
按根 左 右 的順序,依次
前序遍歷根結點->前序遍歷左子樹->前序遍歷右子樹

前序遍歷,遞歸算法
void preorder(bintree t)   
 //注,bintree爲一指向二叉樹根結點的指針
{
   if(t)
    {
      printf("%c",t->data);
      preorder(t->lchild);
      preorder(t->rchild);
    }
}


然後,依葫蘆畫瓢,得到....


2.中序遍歷,遞歸算法
void preorder(bintree t)
{
   if(t)
    {

      inorder(t->lchild);
      printf("%c",t->data);
      inorder(t->rchild);
    }
}

 

3.後序遍歷,遞歸算法
void preorder(bintree t)
{
   if(t)
    {

      postorder(t->lchild);
      postorder(t->rchild);
      printf("%c",t->data);
    }
}

 

二叉樹的創建方法,
void createbintree(bintree* t)
{
  char ch;
  if( (ch=getchar())==' ')
    *t=NULL;
  else
   {
     *t=(bintnode*) malloc(sizeof(bintnode));
     (*t)->data=ch;
     createbintree(&(*t)->lchild);
     createbintree(&(*t)->rchild);
   }
}


接下來,
咱們在討論二叉樹遍歷算法的非遞歸實現之前,
先看一個順序棧的定義及其部分操作的實現
typedef struct stack
{
  bintree data[100];
  int tag[100];
  int top;
}seqstack;

void push(seqstack* s,bintree t)
{
  s->data[s->top]=t;
  s->top++;
}

bintree pop(seqstack* s)   //出棧
{
  if(s->top!=0)
   {
     s->top--;
     return (s->data[s->top]);
   }
  else
     return NULL;
}


好了,現在,我們可以看二叉樹前序遍歷的非遞歸實現了。
按照二叉樹前序遍歷的定義,無論是訪問整棵樹還是其子樹,均應該遵循先訪問根結點,
然後訪問根結點的左子樹,最後訪問根結點的右子樹的。

 

因爲對於一棵樹(子樹)t,如果t非空,訪問完t的根結點值後,就應該進入t的左子樹,
但此時必須將t保存起來,以便訪問完其左子樹後,進入其右子樹的訪問。
yeah,就是這個意思。:)...

即在t處設置一個回溯點,並將該回溯點進棧保存。

 

在整個二叉樹前序遍歷的過程中,程序始終要做的工作分成倆個部分:
1.當前正在處理的樹(子樹)
2.保存在棧中等待處理的部分。

//注:當棧中元素位於棧頂即將出棧時,意味着其根結點和左子樹已訪問完成,
//出棧後,進入其右子樹進行訪問,
//前序遍歷,非遞歸實現
void preorderT(bintree t)
{
  seqstack s;
  s.top=0;
  while( (t)||(s.top!=0) )  //當前處理的子樹不爲空或棧不爲空
  {
    while(t)         //子樹不爲空
    {
      printf("%c",t->data);   //1.先訪問根結點
      push(&s,t);             //2.訪問左子樹之前,記得先把根結點進棧保存
      t=t->lchild;            //3.然後才訪問左子樹,
    }
    if(s.top>0)      //棧不爲空
    {
      t.pop(&s);               //4.pop根結點
      t=t->rchild;             //5.訪問右子樹
    }
  }
}


//中序遍歷,非遞歸實現,
void inorderT(bintree t)
{
  seqstack s;
  s.top=0;
  while( (t)||(s.top!=0) )  //當前處理的子樹不爲空或棧不爲空
  {
    while(t)     //子樹不爲空
    {
      push(&s,t);             //1.訪問左子樹之前,記得先把根結點push進棧
      t=t->lchild;            //2.訪問左子樹
    }
    if(s.top!=0)    //棧不爲空
    {
      t.pop(&s);             //3.pop根結點(訪問完左子樹後)
      printf("%c",t->data);  //4.訪問根結點 (把先前保存的t給拿出來,要用了..)
      t=t->rchild;           //5.訪問右子樹
    }
  }
}

 

//後序遍歷,非遞歸實現
後序遍歷的非遞歸算法,稍微複雜點。請看,

按照二叉樹後序遍歷的定義,無論是訪問整棵樹還是起子樹,
均應該遵循先訪問根結點左子樹,然後訪問根結點的右子樹,最後訪問根結點。

值得注意的是,當一個元素位於棧頂即將處理的是,其左子樹的訪問一定完成,
如果其右子樹不爲空,接下來應該進入其右子樹盡情訪問。
//注意了,
但此時該棧頂元素時不能出棧的,因爲它作爲根結點,其本身的值還未被訪問。
只有等到其右子樹也訪問完成後,該棧頂元素才能出棧,並輸出它的值。

因此,在二叉樹後序遍歷的算法中,必須使用seqstack類型的數組tag,
其每個元素取值爲0或1,用於標識棧中每個元素的狀態。


1.當一個元素剛進棧時,其對應的tag值置爲0;
2.當它位於棧頂即將被處理時,其tag值爲0.意味着應該訪問其右子樹。
於是將右子樹作爲當前處理的對象,此時該棧頂元素仍應該保留在棧中。
並將其對應的tag值改爲1.
3.當其右子樹訪問完成後,該元素又一次位於棧頂,而此時其tag值爲1,
意味着其右子樹已訪問完成,接下來,應該直接訪問的就是它,將其出棧。

 

void postorderT(bintree t)
{
  seqstack s;
  s.top=0;
  while( (t)||(s.top!=0) )
  {
    while(t)
    {
      s.data[s.top]=t;
      s.tag[s.top]=0;   //tag置爲0
      s.top++;
      t=t->lchild;      //訪問左子樹
    }
    while( (s.top>0)&&(s.tag[s.top-1]==1) )       
    {
      s.top--;
      t=s.data[s.top];
      printf("%c",t->data);
    }
    if(s.top>0)
    {
      t=s.data[s.top-1];
      s.tag[s.top-1]=1;
      t=t->rchild;
    }
    else
      t=NULL;
  }
}
至此,完。

 

44.騰訊面試題:
1.設計一個魔方(六面)的程序。
2.有一千萬條短信,有重複,以文本文件的形式保存,一行一條,有重複。
請用5分鐘時間,找出重複出現最多的前10條。
3.收藏了1萬條url,現在給你一條url,如何找出相似的url。(面試官不解釋何爲相似)。

 

關於這題,請看衆網友們給的思路或解答:
beingstudio
1、設計一個魔方(六面)的程序。 
自我感覺用三維座標描述每一個小塊,對面提供旋轉方法,然後每做一個變更就檢測是不是成功了

2、有一千萬條短信,有重複,以文本文件的形式保存,一行一條,
有重複。請用5分鐘時間,找出重複出現最多的前10條。
如果是有序的
讀進來就能出結果;
如果是無序的
建議採用hash或者雙hash歸類,如果想一次完成,還可以維護一個文件排列表

3、收藏了1萬條url,現在給你一條url,如何找出相似的url。(面試官不解釋何爲相似)
例如 http://topic.csdn.net/u/20081029/22/c8fe34c1-25ab-4b94-986e-4c2fd4caa664.html
可以認爲http://topic.csdn.net/u/20081029/22/是相似的
也就是說,我們可以認爲url / 爲相似的,因爲一般對內容歸類也會產生url前面的不同,所以 如果採用二
題的hash算法,可以稍作修改就可

 

jia_xiaoxin
1、設計一個魔方(六面)的程序。 
可以用一個二維數組存儲魔方的面,以及每一個面上的方塊。

2、有一千萬條短信,有重複,以文本文件的形式保存,一行一條,有重複。
請用5分鐘時間,找出重複出現最多的前10條。 
首先我們將文本導入數據庫,使用Having子句來實現這樣的功能,我們利用如下語句
select count(*) ccount from table1
group by a1 having count(*)>1
order by ccount desc
這樣得到的第一個記錄就是出現重複次數最多的那組數字。

 

yangzhongwei1031
個人覺得第二題其實完全可以導入到數據庫中,然後用sql就很容易把結果查出來了。至於說一千萬條查詢
速度很慢的問題,這個完全是可以優化的,這也正好考察了你數據庫的知識。關鍵是看思路,不應該把問題想死了。

第三題找相似的url,什麼是相似的既然沒有講,我覺得也可以用sql來實現,把數據導入到數據庫中,我們
只要按這個字排序就可以了,字符串的排序大家都知道,相同的肯定是在一起的。這樣可以從首字母開始
保證最大限度的相似。

我也剛入行不久,個人想法,也許我的方法不正確,只是覺得程序員這行編碼能力固然重要,
但是想法思維也要活躍些,要懂得用不同的方法去解決問題,不然真的是除了coding還是coding了。

 

ck4918
1,把魔方展開,得到六個正方形,定義六個結構體,內容爲一個9個點和一個編號,每個點包括一個顏色標示;
在魔方展開圖中根據正方形的相鄰關係編號,每個正方形都有四個函數:左翻、右翻、上翻、下翻。
根據相鄰關係,每個操作會引起相鄰面的相關操作;比如一個面的左翻會調用右邊相鄰面的左翻;也就
意味着左相鄰面的0、1、2三個元素與當前面互換;遞歸下去,直到所有面都交換完畢;

2,建立一個紅黑樹a;
遍歷短信,對每條短信取MD5值,對每個MD5值在a中做操作:如果有值,這個key對應的值就+1,否則就=1;
遍歷完後對紅黑樹取值最大的10個數,複雜度爲10lg n

3.正則表達式,呵


zhenming_liu
1、設計一個魔方(六面)的程序。 
這個就是一點簡單的線性代數了。
2、有一千萬條短信,有重複,以文本文件的形式保存,一行一條,有重複。
請用5分鐘時間,找出重複出現最多的前10條。
 
同學最近寫了一篇論文,好像是解這個的 
http://www.cse.ust.hk/~qinzhang/papers/fp337-zhang.pdf
他的模型好像比問題複雜,但是即便是最簡單的streamming模型也是這十年纔有人做的。

3、收藏了1萬條url,現在給你一條url,如何找出相似的url。(面試官不解釋何爲相似)
這些問題來來去去就是Hashing啦。如果是Hamming distance, 應該是能建造Locality sensitive hashing的(http://en.wikipedia.org/wiki/Locality_sensitive_hashing),
 如果是edit distance的話,應該還是沒有人建構的出來對應的Hash Function
(其實這也是把edit distance的測度空間嵌入到L_1的測度空間,我印象中好像是做不來的).

 

elovenana
1、設計一個魔方(六面)的程序。
typedef struct color
{
  int r,g,b;
} color;
typedef struct omf
{
  int 面[6],
  color cl; 
}mf;
2、有一千萬條短信,有重複,以文本文件的形式保存,一行一條,有重複。
請用5分鐘時間,找出重複出現最多的前10條。

這個可以,先取出第一條,然後,存入變量並將此條刪除,與下面的比較,遇到相同的刪除,
並且,計數器加一,然後,寫入到另一個文件,標題和次數;重複之;直到清空文件;
然後,去比較另一個文件的次數,即可;

3、收藏了1萬條url,現在給你一條url,如何找出相似的url。(面試官不解釋何爲相似)
現在有許多的庫文件都支持,正則表達式,只要用正則去匹配就可以了;

 

yinghan2005
2、有一千萬條短信,有重複,以文本文件的形式保存,一行一條,有重複。
請用5分鐘時間,找出重複出現最多的前10條。

首先我們將文本導入數據庫,使用Having子句來實現這樣的功能,我們利用如下語句 select count(*)
ccount from table1 group by a1 having count(*)>1 order by ccount desc
這樣得到的第一個記錄就是
出現重複…

導入數據庫很費時間的呀,5分鐘絕對不夠。

第二個問題,shell解決,不知道效率如何,
sort messages.txt |uniq -c |sort -k1 |tail -10

----------------------------------
更多,請參考:
http://topic.csdn.net/u/20081029/22/c8fe34c1-25ab-4b94-986e-4c2fd4caa664.html?11622
http://blog.csdn.net/lijiaz5033/archive/2008/11/05/3226574.aspx

還是第44題:
下文作者,lijiaz5033
第一題魔方
其實也很簡單!
先說面,每面有四邊,因此要建立4個句柄對應:上下左右,四個去面,就像雙向鏈表節點有上下兩面一樣,
然後面裏有方塊矩陣,2級的數組,加完數組寫個方法函數,叫旋轉,參數是行列號/旋向 ,
旋向上下時行列號爲行號,旋向左右時行列號爲列號,意思是把某行某列往某方向旋轉。

矩陣裏有方塊,方塊有 創建六個面,按照魔方的樣子,將第一面爲正面,往下是底面(把第二面拿過來),
底面往下是背面,背面往下聯就是上面,上面往下是正面,現在回到正面,正面往左聯就是左面,
左面往左聯就是後面,後面往左就是右面,右面往左是正面。。。。。。(這裏不用羅索了,自己看明白了就知道怎麼做了)

六個面創建完並上下左右連通後,這個程序就完成了

 

第2小題:
首先,一千萬條短信按現在的短信長度將不會超過700M(平均情況下應該是350M),
使用內存映射文件比較合適.可以一次映射(當然如果更大的數據量的話,可以採用分段映射),
由於不需要頻繁使用文件I/O和頻繁分配小內存,這將大大提高了數據的加載速度.
其次,對每條短信的第i(i從0到70)個字母按ASCII碼進行分組,
其實也就是創建樹.i是樹的深度,也是短信第i個字母.
//樹結點定義
struct TNode
{
  BYTE* pText;//直接指向文件映射的內存地址,使用BYTE而不用char是爲符號問題
  DWORD dwCount;//計算器,記錄此結點的相同短信數
  TNode* ChildNodes[256]; //子結點數據,由於一個字母的ASCII值不可能超過256,所以子結點也不可能超過256

  TNode()
  {
    //初始化成員
  }
  ~TNode()
  {
    //釋放資源
  }
};

//BYTE* pText直接指向文件映射的內存地址,使用BYTE而不用char是爲符號問題
//int nIndex是字母下標
void CreateChildNode(TNode* pNode, const BYTE* pText, int nIndex)
{
    if(pNode->ChildNodes[pText[nIndex]] == NULL)
    {//如果不存在此子結點,就創建.TNode構造函數應該有初始化代碼
      //爲了處理方便,這裏也可以在創建的同時把此結點加到一個數組中.
      pNode->ChildNodes[pText[nIndex]] = new TNode;
    }

    if(pText[nIndex+1] == '/0')
    {//此短信已完成,記數器加1,並保存此短信內容
      pNode->ChildNodes[pText[nIndex]]->dwCount++;
      pNode->ChildNodes[pText[nIndex]]->pText = pText;
    }
    else //if(pText[nIndex] != '/0')
    {//如果還未結束,就創建下一級結點
        CreateNode(pNode->ChildNodes[pText[nIndex]], pText, nIndex+1);
    }
}

//創建根結點,pTexts是短信數組,dwCount是短信數量(這裏是一千萬)
void CreateRootNode(const BYTE** pTexts, DWORD dwCount)
{
    TNode RootNode;
    for(DWORD dwIndex=0;dwIndex <dwCount;dwIndex++)
    {
        CreateNode(&RootNode, pTexts[dwIndex], 0);
    }
    //所有結點按dwCount的值進行排序
    //代碼略...
 
    //取前10個結點,顯示結果
    //代碼略...
}
這樣處理看起來很複雜,其實就是爲了減少比較次數.我認爲大家看了這小段代碼應該可以明白我的意思了,
其它的不多說了.
最後,就是對這些結點按dwCount的值進行排序,取前面的前10個結點就可以了.

我認爲這問題只要是解決兩方面的內容,一是內容加載,二是短信內容比較.
採用文件內存映射技術可以解決內容加載的性能問題(不僅僅不需要調用文件I/O函數,
而且也不需要每讀出一條短信都要分配一小塊內存),而使用樹技術可以有效減少比較的次數.
當然基本思路是這樣,如果有心情還可以在這基礎上做一些優化處理,效果一定不會差的。
第3小題,略。


再看,第2小題:
2、有一千萬條短信,有重複,以文本文件的形式保存,一行一條,有重複。
請用5分鐘時間,找出重複出現最多的前10條。

iwillalwaysloveyou
1.短信長度是有限的,例如在中國短信長度範圍爲0-140字節,
2.題目中沒有提到內存限制,假設內存是足夠的(本題按下面算法最壞情況下需要1個多G)
2.建立140個元素的multimap數組(空短信可另行特殊處理),
下標爲i的multimap與長度爲i的字符串相對應。鍵爲字符串的hash,值爲字符串及其出現次數.
3.遍歷短信,將短信根據長度進行處理,怎麼處理我就不細說了
4.對每一個multimap,按字符串的出現次數,找出前10個字符串(也可能不足10個),
(可以用堆排序,複雜度爲O(n*logn))
5.在4找出的所有字符串組成的集合中,按字符串的出現次數,找出出現最多的前10個


此題題目好像有點兒問題,次數最多的有可能不是剛好10個,例如有9個字符串各出現10次,有2個字符串出現9次,其他均小於9次。


July個人認爲,建立R-B樹,挺好的,如ck4918所說,
2,建立一個紅黑樹a;遍歷短信,對每條短信取MD5值,對每個MD5值在a中做操作:
如果有值,這個key對應的值就+1,否則就=1;遍歷完後對紅黑樹取值最大的10個數,複雜度爲10*lgn。
---------------------------------------------------------------------------------------

 

接下來,請一次性,看第45-48題:
  //注,此第45-48題的答案,僅僅只作爲你思路的參考。
爲了表示簡明,以下我引用網友答案時,1、2、3、4、5即與以下的第45-48題,一一對應:.

雅虎:
1、對於一個整數矩陣,存在一種運算,對矩陣中任意元素加一時,需要其相鄰(上下左右)
某一個元素也加一,現給出一正數矩陣,判斷其是否能夠由一個全零矩陣經過上述運算得到。
2、.一個整數數組,長度爲n,將其分爲m份,使各份的和相等,求m的最大值
  比如{3,2,4,3,6} 可以分成{3,2,4,3,6} m=1;
  {3,6}{2,4,3} m=2
  {3,3}{2,4}{6} m=3 所以m的最大值爲3

3、.搜狐:
4對括號可以有多少種匹配排列方式?比如兩對括號可以有兩種:()()和(())

4、創新工場:
求一個數組的最長遞減子序列 比如{9,4,3,2,5,4,3,2}的
最長遞減子序列爲{9,5,4,3,2}

5、微軟:
一個數組是由一個遞減數列左移若干位形成的,比如{4,3,2,1,6,5}
是由{6,5,4,3,2,1}左移兩位形成的,在這種數組中查找某一個數。

在此,只重點給出第4題,最長遞減子序列的答案:
4、創新工場:最長遞減子序列
求一個數組的最長遞減子序列 比如{9,4,3,2,5,4,3,2}的
最長遞減子序列爲{9,5,4,3,2}

 

======================================================================
關於這5道題,看網友們給的回覆[僅供參考]:
fire_woods
1. 有2個弱判斷準則,不過不知道3個加起來是不是和題目等價
  1) 講矩陣分成黑白棋盤格, 所有黑色格子的數字和等於白色格子的.
  2)對任意一個位置, 他的值不大於周圍(上下左右)4個臨格的數值的和.
2. 揹包問題, 當然有些條件可以預先判斷,比如(所有元素的和%m)!=0, 那麼就不需要處理了.
3.1*1+3*3+2*2=14
4.直接遍歷, O(n)
5.二分法查找分界點, 再二分法查找O(ln(n))。

 

duguyue200
1.第一反應是廣搜,因爲以前遇過一個這樣類似的問題,應該是變式之類的。。
2.第一反應有數學方法,結合爆搜吧。一定是可以解的不過搜索量不小,可能用深搜好點。
3.第一反應是排列組合。因爲規則給的很多。
4.第一反應就是動歸,沒說的,經典問題。
5.第一反應是因爲有局部的遞減性質,那就找到斷點,不要恢復(因爲恐怕有陰人的地方,不是一次能夠恢復得了的)。局部用二分查找。

 

wds530405973
第2題 算法 原理的思想是將大問題轉換成小問題。
就{3,2,4,3,6}的操作步驟:
  第一步:想將數組遞減排序得{6,4,3,3,2},求出數組中所有數的和m=18,第一個最大的數b=6, m/b=3餘數爲0,當除數爲1,餘數爲0時終止。當餘數不爲0時,轉到第三步。當餘數爲0時將數組劃分爲{6},{4,3,3,2}兩個。把{4,3,3,2}看成一個新的數組。
  第二步:先用{4,3,3,2}中的最大數與b=6比較,即4<b,所以再將4與最右邊的數即2相加與b比較,結果相等,則將這兩個數從該數組中除去生成新的數組,轉到第一步,現在的結果是{6},{4,2},{3,3},把{3,3}看成一個新的數組繼續重複第二步。
  第三步,將數組中最大的數與最小的數取出構成一個新數組Z,剩餘的構成一個數組,然後,判斷m/Z中數字之和看是否餘數爲0,若爲0,把b替換爲Z中數字之和轉第二步,若不爲0,繼續從剩餘的數字中取出最小值加入到Z中,再判斷m/Z中數字之和看是否餘數爲0,直到爲0,轉第二步爲止。
最後得到的結果是{6},{4,2},{3,3} 這時可以計算出m爲3,也可以在程序中作記載。
在第二步工程過,若出現兩個數相加大於上一次的b,則將程序轉到第三步。

 

Beiyouyu
5. 二分解決。注意到a[0] <= a[n-1],設要查找的是key、那麼a[mid]、key、a[0]、a[n-1]可以確定一個順序,將原數組切分成兩部分:1部分是純遞減數組、另一部分是原問題的子問題。
具體如下:
如果a[mid] <= a[0] :那麼a[0...mid]是純遞減數組;可以簡單比較出key是否在a[0..mid]中,如果在那麼問題就是標準的二分查找了;否則,a[mid+1...n-1]就是原問題的子問題了。
如果a[mid] >= a[n-1]: 那麼a[mid...n-1]也是純遞減數組;與上類似。

 

Zhifeidie
第1題思路:動態規劃,遞歸
1、先判斷矩陣中的每一個值,是否可以以其爲中心減1,也就是這個值和它的上下左右都大於1,如果成立,則以此值爲中心包括上下左右減1,對生成的新的矩陣遞歸計算。退出條件:全0表示成功,如果不是全0但是所有的值都不滿足前面的條件,返回失敗。

第2題思路:動態規劃,遞歸

將整個數組作爲一個集合,最大的可能值就是集合的大小了,最小肯定是1,那麼從2開始一次判斷。如果集合可被k等分,那麼首先集合的和能夠被k整除,如果這個條件滿足,則重複k-1次從這個集合中取出和爲sum/k的子集合。
取子集合的算法是一個遞歸的思想,詳見153樓
其他幾個題目都是比較經典的問題,不贅述。


============================
以下,直到出現下一條槓槓"==========="之前,寫的都是第4題。
pmars
說一下第四題:
一道做過的ACM題:
想法是弄一個數組存放已經找到的最長子序列,當然裏面的元素都是找到的在各個位置上的最大的值(記得以前我發過一個這個題目的帖子)
http://topic.csdn.net/u/20100424/22/7a7b4a50-9110-4cf8-96ec-7fa728593b15.html
能做到NlogN吧!


litaoye
二部圖是什麼?二分圖?

這個問題就是一個最大遞增序列問題,最普通的想法就是用DP,n^2肯定可以(類似於LCS問題)。當然還可以優化到n*log(n),那是另一回事兒了。

 

litaoye
代碼網上一大堆,說說思路吧,以4 2 6 3 1 5爲例

逐個讀入數字,

4 | 此時可能的隊列長度爲1,最大值爲4
4 2 | 由於2 < 4,此時隊列長度爲1,最大值爲2
4 2 6 | 6 > 2,隊列有2個,一個長度爲1,最大爲2,一個長度爲2,最大爲6
4 2 6 3 | 3 < 6, 3 > 2,隊列有2個,一個長度爲1,最大爲2,一個長度爲2,最大爲3
4 2 6 3 1 | 1 < 2, 1 < 3,隊列有2個,一個長度爲1,最大爲1,一個長度爲2,最大爲3
4 2 6 3 1 5 | 5 > 1,5 > 3,隊列有3個,一個長度爲1,最大爲1,一個長度爲2,最大爲3,一個長度爲3,最大爲5(分別是1 | 2,3 | 2,3,5)

走到頭了,所以輸出3,此時是一個最壞情況下n^2的算法,但如果每讀入一個新的數時,不是逐個比較,而是利用二分法,查到小於該數的最長序列,那麼就是n*log(n)的方法了。

 

litaoye
還是貼一段簡單的代碼吧,給自己留個備份,臨時寫的,希望沒有錯,C#的。
using System;
namespace csdnTest
{
    class Program
    {
        static void Main(string[] args)
        {
            int[] items = new int[] { 4, 2, 6, 3, 1, 2, 5 };

            //用來記錄序列長隊最大值的數組,index + 1就是序列的長度
            int[] maxValues = new int[items.Length];
            int maxLength = 1;
            maxValues[0] = items[0];

            for (int i = 1; i < items.Length; i++)
            {
                //二分查找對應的最長序列
                int lengthIndex = Array.BinarySearch<int>(maxValues, 0, maxLength, items[i]);
               
                //針對於.Net裏面的BinarySearch的特殊處理,不用理會
                if (lengthIndex < 0)
                    lengthIndex = -lengthIndex - 1;

                if (lengthIndex + 1 > maxLength)
                    maxLength = lengthIndex + 1;

                maxValues[lengthIndex] = items[i];
            }

            Console.WriteLine(maxLength);
        }
    }
}
第4題完。
======================================================

 

 

thegodofwar
大公司都愛考些“動態規劃”的題,因爲“動態規劃”解法不定,
可能用到多種其它經典的算法(比如遞歸、回溯、二分、修枝......)

 

higherone
第一題很多人都沒理解對。題目中說的是元素加一的時候,相鄰的四個元素的某一個必須加一(四個中的任一個都可以),而不是四個相鄰元素都加一。

  感覺2樓提出的2個弱判斷準則還挺有道理的。給出代碼的解答我都沒看,還是願意看解題思路,動不動給代碼的,多半都沒搞清楚問題。
--------------------
 //這裏說的2樓給的2個弱判斷準則,是這:-------


  fire_woods
  有2個弱判斷準則,不過不知道3個加起來是不是和題目等價
  1) 講矩陣分成黑白棋盤格, 所有黑色格子的數字和等於白色格子的.
  2)對任意一個位置, 他的值不大於周圍(上下左右)4個臨格的數值的和.
----------------------------------------------------------
  如果這兩個準則不是充要條件,那麼我只想到一個回溯的方法。從某個元素開始,嘗試找一個他的鄰居與他成爲一組,共同減一。這算一步操作。如果這樣減下去,最終能達到0矩陣,則有解;否則回溯到上一步,找其它鄰居成爲一組,共同減一。
  但是這個回溯的方法在這裏有很大的弊病:回溯次數不僅跟矩陣階數有關,還跟矩陣元素值有關,元素值越大,就越多回溯次數。所以這個算法肯定是不靠譜的。

 

higherone
二分最大匹配,不是求匹配的最大數目麼?怎麼在這個題目中應用?
另外,本題中的匹配,還不能是任意兩個黑白子的匹配,而是相鄰的黑白子才能匹配。是不是用起來會有問題?

接上:
litaoye
每個黑點只跟周圍相鄰的白點聯通,白點也只跟周圍相鄰的黑點聯通,有一個共同的源S連到黑點,每條邊的容量就是黑點上面的數字,黑點同白點之間的連線,容量都看作無窮大,所有白點都連到一個共同的匯點T,權值就是白點上面的數字,如果從源S到T之間的最大流=所有黑點上面的數字和 同時=所有白點上面的數字和,那麼該矩陣就是可以被還原的,以上是最大流的解法,肯定可以得出正確的結果,但至於是否爲最優方法,就不一定了,這題看起來用類似貪心的思路興許也成,不過沒證明過,也沒仔細想過反例,最近針對於這類黑白染色的問題,我總犯錯誤,只好老老實實的用最大流了。

 

higherone
牛!
剛看了下最大流算法的資料,理解了一下,真的是可以解決問題的。
說下我的理解,大家指正:
  用黑點連線,指向相鄰的白點,模擬了黑點和白點組合在一起加一的情況,並設該連線容量無窮大,是說流量在這裏不是瓶頸。
  源點S的流量,通過黑點,再通過白點,最後到達匯點。“最大流量=黑點之和=白點之和”,實際上是說源點到黑點的流量,最終都流到白點到匯點的連線上了,也就是黑點的每個1都找到了一個白點與之組合。
因此這個條件就等價與原題目。
  那麼,剛纔那個最大匹配的做法不靠譜了?
=======================

 

lanhaibin
微軟:
5.一個數組是由一個遞減數列左移若干位形成的,比如{4,3,2,1,6,5}是由{6,5,4,3,2,1}左移兩位形成的,在這種數組中查找某一個數。
先將數組右移恢復爲原來的遞減數列,然後運用二分查找即可。

 

jinwen0915
第三題:12種
1 () () () ()
2 (()) () ()
3 () (()) ()
4 () () (())
5 () (() ())
6 (() () ())
7 ((()) ())
8 ((()) ())
9 (() ()) ()
10((())) ()
11(()) (())
12() ((()))

 

lmgsdu
關於第一題,感覺類似七橋問題。個人有個解法不知道對不對。
假設正數矩陣每一個元素都是大於0的。
求矩陣每一行與列的和,滿足以下兩個條件即可由全零矩陣得到:
1、如果行與列的和有爲奇數的,那麼必須分別爲偶數個。
2、在所有和爲奇數的行與列中,相鄰的奇數和不能爲偶數對(頂角處的奇數行與奇數列不算做相鄰)
還原假設條件,如果矩陣中存在0,且能夠將矩陣分割成幾個由正整數組成的小矩陣,則對小矩陣套用以上兩個條件即可。
個人想法。

第45題至第48題完。
由於這些題的思路,都是整理各個網友的,很亂,見諒。
至於是否正確,請自己辨明。
也希望,看到此份資源的答案,如果有更好的思路、解答,請務必與我聯繫。

=====================================================================

 


49.一道看上去很嚇人的算法面試題:
如何對n個數進行排序,要求時間複雜度O(n),空間複雜度O(1)
此題請看下文,作者張羿:
看上去似乎任何已知的算法都無法做到,如果誰做到了,那麼所有的排序方法:QuickSort,ShellSort,HeapSort,BubbleSort等等等等,都可以扔掉了,還要這些算法幹嗎阿,呵呵。不過實際上,在數字範圍有限制的情況下,是有一個這樣的算法的,只需要用一個數組記錄每個數字出現次數就可以了。
假定你的數字範圍在0到65535範圍之內,定義一個數組count[65536](這個空間是常量,和n無關,所以是O(1) ),初值全部爲0。
那麼假設有下面這些數字:
100
200
300
119
0
6
...
那麼對於每個這個數字,都做在count中記錄一下:
100 => count[100]++
200 => count[200]++
300 => count[300]++
119 => count[119]++
0 => count[0]++
6 => count[6]++
...
最後,遍歷一邊所有這些數字就可得到0~65535每個數字的個數(在count數組中),然後再順序遍歷count數組,count[n] = m,則輸出m個n,(比如說有count[3] = 2, 那麼說明有2個數字3),依次輸出,最後可得結果。第一次遍歷是O(n),第二次遍歷是O(1),爲常量,所以最後的時間複雜度爲O(n),而空間複雜度爲O(1)
這個算法很簡單,相信大家都會,只是這個題太過於變態了,一般會把面試者嚇住(我原來面試也出過這個題,只不過題目的表述形式要“友善”的多,呵呵)

 

 

50.網易有道筆試:
1.求一個二叉樹中任意兩個節點間的最大距離,兩個節點的距離的定義是 這兩個節點間邊的個數,比如某個孩子節點和父節點間的距離是1,和相鄰兄弟節點間的距離是2,優化時間空間複雜度。
2.求一個有向連通圖的割點,割點的定義是,如果除去此節點和與其相關的邊,有向圖不再連通,描述算法。
第1小題,就是本微軟等面試100題系列,第11題。
//請參考答案V0.3版。上有我給的詳細思路闡述。

I am very,sorry,此刻在整理答案時,我才發現,原來這第50題與本微軟等100題系列第39題重複了。
非常抱歉。望衆位見諒。
此題,請參考答案V0.3版(第20-40題的答案)。


----------------------------------------------------------------
以下的10題,第51題-60題答案參考,皆整理自網友何海濤博客。
何海濤CSDN主頁:http://hi.csdn.net/cadcisdhht
51.和爲n連續正數序列。
題目:輸入一個正數n,輸出所有和爲n連續正數序列。
例如輸入15,由於1+2+3+4+5=4+5+6=7+8=15,所以輸出3個連續序列1-5、4-6和7-8。


分析:這是網易的一道面試題。
這道題和本微軟面試100題系列V0.1版的第14題有些類似。

我們可用兩個數small和big分別表示序列的最小值和最大值。首先把small初始化爲1,big初始化爲2。
如果從small到big的序列的和大於n的話,我們向右移動small,相當於從序列中去掉較小的數字。
如果從small到big的序列的和小於n的話,我們向右移動big,相當於向序列中添加big的下一個數字。
一直到small等於(1+n)/2,因爲序列至少要有兩個數字。

 

基於這個思路,我們可以寫出如下代碼:
void PrintContinuousSequence(int small, int big);

// Find continuous sequence, whose sum is n
void FindContinuousSequence(int n)
{
      if(n < 3)
            return;

      int small = 1;
      int big = 2;
      int middle = (1 + n) / 2;
      int sum = small + big;

      while(small < middle)
      {
            // we are lucky and find the sequence
            if(sum == n)
                  PrintContinuousSequence(small, big);

            // if the current sum is greater than n,
            // move small forward
            while(sum > n)
            {
                  sum -= small;
                  small ++;

                  // we are lucky and find the sequence
                  if(sum == n)
                        PrintContinuousSequence(small, big);
            }

            // move big forward
            big ++;
            sum += big;
      }
}

// Print continuous sequence between small and big
void PrintContinuousSequence(int small, int big)
{
      for(int i = small; i <= big; ++ i)
            printf("%d ", i);

      printf("/n");
}

 


52.二元樹的深度。
題目:輸入一棵二元樹的根結點,求該樹的深度。
從根結點到葉結點依次經過的結點(含根、葉結點)形成樹的一條路徑,
最長路徑的長度爲樹的深度。
例如:輸入二元樹:
                                            10
                                          /     /
                                        6        14
                                      /         /   /
                                    4         12     16

輸出該樹的深度3。

二元樹的結點定義如下:
struct SBinaryTreeNode // a node of the binary tree
{
      int               m_nValue; // value of node
      SBinaryTreeNode  *m_pLeft;  // left child of node
      SBinaryTreeNode  *m_pRight; // right child of node
};
分析:這道題本質上還是考查二元樹的遍歷。

題目給出了一種樹的深度的定義。當然,我們可以按照這種定義去得到樹的所有路徑,也就能得到最長路徑以及它的長度。只是這種思路用來寫程序有點麻煩。

 

我們還可以從另外一個角度來理解樹的深度。如果一棵樹只有一個結點,它的深度爲1。
如果根結點只有左子樹而沒有右子樹,那麼樹的深度應該是其左子樹的深度加1;
同樣如果根結點只有右子樹而沒有左子樹,那麼樹的深度應該是其右子樹的深度加1。
如果既有右子樹又有左子樹呢?那該樹的深度就是其左、右子樹深度的較大值再加1。

 

上面的這個思路用遞歸的方法很容易實現,只需要對遍歷的代碼稍作修改即可。

參考代碼如下:
// Get depth of a binary tree
// Input: pTreeNode - the head of a binary tree
// Output: the depth of a binary tree
int TreeDepth(SBinaryTreeNode *pTreeNode)
{
      // the depth of a empty tree is 0
      if(!pTreeNode)
            return 0;

      // the depth of left sub-tree
      int nLeft = TreeDepth(pTreeNode->m_pLeft);
      // the depth of right sub-tree
      int nRight = TreeDepth(pTreeNode->m_pRight);

      // depth is the binary tree
      return (nLeft > nRight) ? (nLeft + 1) : (nRight + 1);
}

 

 

53.字符串的全排列。
題目:輸入一個字符串,打印出該字符串中字符的所有排列。
例如輸入字符串abc,則輸出由字符a、b、c所能排列出來的所有字符串
abc、acb、bac、bca、cab和cba。

分析:這是一道很好的考查對遞歸理解的編程題,
因此在過去一年中頻繁出現在各大公司的面試、筆試題中。

 

我們以三個字符abc爲例來分析一下求字符串排列的過程。
首先我們固定第一個字符a,求後面兩個字符bc的排列。
當兩個字符bc的排列求好之後,我們把第一個字符a和後面的b交換,得到bac,
接着我們固定第一個字符b,求後面兩個字符ac的排列。

現在是把c放到第一位置的時候了。記住前面我們已經把原先的第一個字符a和後面的b做了交換,
爲了保證這次c仍然是和原先處在第一位置的a交換,我們在拿c和第一個字符交換之前,先要把b和a交換回來。
在交換b和a之後,再拿c和處在第一位置的a進行交換,得到cba。

我們再次固定第一個字符c,求後面兩個字符b、a的排列。
既然我們已經知道怎麼求三個字符的排列,那麼固定第一個字符之後求後面兩個字符的排列,就是典型的遞歸思路了。

 

即,
固定a,=>a bc,a cb
固定b,=>b ac,b ca
固定c,=>c ab,c ba。

 

基於以上分析,我們可以得到如下的參考代碼:
void Permutation(char* pStr, char* pBegin);

// Get the permutation of a string,
// for example, input string abc, its permutation is
// abc acb bac bca cba cab
void Permutation(char* pStr)
{
      Permutation(pStr, pStr);
}

// Print the permutation of a string,
// Input: pStr   - input string
//        pBegin - points to the begin char of string
//                 which we want to permutate in this recursion
void Permutation(char* pStr, char* pBegin)
{
      if(!pStr || !pBegin)
            return;

      // if pBegin points to the end of string,
      // this round of permutation is finished,
      // print the permuted string
      if(*pBegin == '/0')
      {
            printf("%s/n", pStr);
      }
      // otherwise, permute string
      else
      {
            for(char* pCh = pBegin; *pCh != '/0'; ++ pCh)
            {+
                  // swap pCh and pBegin
                  char temp = *pCh;
                  *pCh = *pBegin;
                  *pBegin = temp;

                  Permutation(pStr, pBegin + 1);

                  // restore pCh and pBegin
                  temp = *pCh;
                  *pCh = *pBegin;
                  *pBegin = temp;
            }
      }
}

擴展1:如果不是求字符的所有排列,而是求字符的所有組合,應該怎麼辦呢?
當輸入的字符串中含有相同的字符串時,相同的字符交換位置是不同的排列,但是同一個組合。
舉個例子,如果輸入aaa,那麼它的排列是6個aaa,但對應的組合只有一個。

擴展2:輸入一個含有8個數字的數組,判斷有沒有可能把這8個數字分別放到正方體的8個頂點上,
使得正方體上三組相對的面上的4個頂點的和相等。

 


54.調整數組順序使奇數位於偶數前面。
題目:輸入一個整數數組,調整數組中數字的順序,使得所有奇數位於數組的前半部分,
所有偶數位於數組的後半部分。要求時間複雜度爲O(n)。

分析:如果不考慮時間複雜度,最簡單的思路應該是從頭掃描這個數組,每碰到一個偶數時,拿出這個數字,並把位於這個數字後面的所有數字往前挪動一位。挪完之後在數組的末尾有一個空位,這時把該偶數放入這個空位。由於碰到一個偶數,需要移動O(n)個數字,因此總的時間複雜度是O(n2)。

 

要求的是把奇數放在數組的前半部分,偶數放在數組的後半部分,因此所有的奇數應該位於偶數的前面。也就是說我們在掃描這個數組的時候,如果發現有偶數出現在奇數的前面,我們可以交換他們的順序,交換之後就符合要求了。

因此我們可以維護兩個指針,第一個指針初始化爲數組的第一個數字,它只向後移動;第二個指針初始化爲數組的最後一個數字,它只向前移動。在兩個指針相遇之前,第一個指針總是位於第二個指針的前面。如果第一個指針指向的數字是偶數而第二個指針指向的數字是奇數,我們就交換這兩個數字。

 

基於這個思路,我們可以寫出如下的代碼:

void Reorder(int *pData, unsigned int length, bool (*func)(int));
bool isEven(int n);

// Devide an array of integers into two parts, odd in the first part,
// and even in the second part
// Input: pData  - an array of integers
//        length - the length of array

void ReorderOddEven(int *pData, unsigned int length)
{
      if(pData == NULL || length == 0)
            return;

      Reorder(pData, length, isEven);
}

// Devide an array of integers into two parts, the intergers which
// satisfy func in the first part, otherwise in the second part
// Input: pData  - an array of integers
//        length - the length of array
//        func   - a function
void Reorder(int *pData, unsigned int length, bool (*func)(int))
{
      if(pData == NULL || length == 0)
            return;

      int *pBegin = pData;
      int *pEnd = pData + length - 1;

      while(pBegin < pEnd)
      {
            // if *pBegin does not satisfy func, move forward
            if(!func(*pBegin))
            {
                  pBegin ++;
                  continue;
            }

            // if *pEnd does not satisfy func, move backward
            if(func(*pEnd))
            {
                  pEnd --;
                  continue;
            }

            // if *pBegin satisfy func while *pEnd does not,
            // swap these integers
            int temp = *pBegin;
            *pBegin = *pEnd;
            *pEnd = temp;
      }
}

// Determine whether an integer is even or not
// Input: an integer
// otherwise return false
bool isEven(int n)
{
      return (n & 1) == 0;
}

討論:
上面的代碼有三點值得提出來和大家討論:
1.函數isEven判斷一個數字是不是偶數並沒有用%運算符而是用&。理由是通常情況下位運算符比%要快一些;
2.這道題有很多變種。
這裏要求是把奇數放在偶數的前面,如果把要求改成:把負數放在非負數的前面等,思路都是都一樣的。
3.在函數Reorder中,用函數指針func指向的函數來判斷一個數字是不是符合給定的條件,而不是用在代碼直接判斷(hard code)。這樣的好處是把調整順序的算法和調整的標準分開了(即解耦,decouple)。當調整的標準改變時,Reorder的代碼不需要修改,只需要提供一個新的確定調整標準的函數即可,提高了代碼的可維護性。

例如要求把負數放在非負數的前面,我們不需要修改Reorder的代碼,只需添加一個函數來判斷整數是不是非負數。
這樣的思路在很多庫中都有廣泛的應用,比如在STL的很多算法函數中都有一個仿函數(functor)的參數(當然仿函數不是函數指針,但其思想是一樣的)。
如果在面試中能夠想到這一層,無疑能給面試官留下很好的印象。
 

 

55.
題目:類CMyString的聲明如下:
class CMyString
{
public:
      CMyString(char* pData = NULL);
      CMyString(const CMyString& str);
      ~CMyString(void);
      CMyString& operator = (const CMyString& str);

private:
      char* m_pData;
};
請實現其賦值運算符的重載函數,要求異常安全,即當對一個對象進行賦值時發生異常,對象的狀態不能改變。

分析:首先我們來看一般C++教科書上給出的賦值運算符的重載函數:
CMyString& CMyString::operator =(const CMyString &str)
{
      if(this == &str)
            return *this;

      delete []m_pData;
      m_pData = NULL;

      m_pData = new char[strlen(str.m_pData) + 1];
      strcpy(m_pData, str.m_pData);

      return *this;
}

我們知道,在分配內存時有可能發生異常。當執行語句new char[strlen(str.m_pData) + 1]發生異常時,程序將從該賦值運算符的重載函數退出不再執行。注意到這個時候語句delete []m_pData已經執行了。也就是說賦值操作沒有完成,但原來對象的狀態已經改變。也就是說不滿足題目的異常安全的要求。

爲了滿足異常安全這個要求,一個簡單的辦法是掉換new、delete的順序。先把內存new出來用一個臨時指針保存起來,只有這個語句正常執行完成之後再執行delete。這樣就能夠保證異常安全了。

下面給出的是一個更加優雅的實現方案:
CMyString& CMyString::operator =(const CMyString &str)
{
      if(this != &str)
      {
            CMyString strTemp(str);

            char* pTemp = strTemp.m_pData;
            strTemp.m_pData = m_pData;
            m_pData = pTemp;
      }
      return *this;
}

該方案通過調用構造拷貝函數創建一個臨時對象來分配內存。此時即使發生異常,對原來對象的狀態沒有影響。交換臨時對象和需要賦值的對象的字符串指針之後,由於臨時對象的生命週期結束,自動調用其析構函數釋放需賦值對象的原來的字符串空間。整個函數不需要顯式用到new、delete,內存的分配和釋放都自動完成,因此代碼顯得比較優雅。

 


56.最長公共子序列。
題目:如果字符串一的所有字符按其在字符串中的順序出現在另外一個字符串二中,
則字符串一稱之爲字符串二的子串。

注意,並不要求子串(字符串一)的字符必須連續出現在字符串二中。
請編寫一個函數,輸入兩個字符串,求它們的最長公共子串,並打印出最長公共子串。
例如:輸入兩個字符串BDCABA和ABCBDAB,字符串BCBA和BDAB都是是它們的最長公共子串,
則輸出它們的長度4,並打印任意一個子串。

 

分析:求最長公共子串(Longest Common Subsequence, LCS)是一道非常經典的動態規劃題,
因此一些重視算法的公司像MicroStrategy都把它當作面試題。

完整介紹動態規劃將需要很長的篇幅,因此我不打算在此全面討論動態規劃相關的概念,只集中對LCS直接相關內容作討論。如果對動態規劃不是很熟悉,請參考相關算法書比如算法討論。

 

先介紹LCS問題的性質:記Xm={x0, x1,…xm-1}和Yn={y0,y1,…,yn-1}爲兩個字符串,而Zk={z0,z1,…zk-1}是它們的LCS,則:

1.       如果xm-1=yn-1,那麼zk-1=xm-1=yn-1,並且Zk-1是Xm-1和Yn-1的LCS;
2.       如果xm-1≠yn-1,那麼當zk-1≠xm-1時Z是Xm-1和Y的LCS;
3.       如果xm-1≠yn-1,那麼當zk-1≠yn-1時Z是Yn-1和X的LCS;

 

下面簡單證明一下這些性質:
1.       如果zk-1≠xm-1,那麼我們可以把xm-1(yn-1)加到Z中得到Z’,這樣就得到X和Y的一個長度爲k+1的公共子串Z’。這就與長度爲k的Z是X和Y的LCS相矛盾了。因此一定有zk-1=xm-1=yn-1。
既然zk-1=xm-1=yn-1,那如果我們刪除zk-1(xm-1、yn-1)得到的Zk-1,Xm-1和Yn-1,顯然Zk-1是Xm-1和Yn-1的一個公共子串,現在我們證明Zk-1是Xm-1和Yn-1的LCS。用反證法不難證明。假設有Xm-1和Yn-1有一個長度超過k-1的公共子串W,那麼我們把加到W中得到W’,那W’就是X和Y的公共子串,並且長度超過k,這就和已知條件相矛盾了。
2.       還是用反證法證明。假設Z不是Xm-1和Y的LCS,則存在一個長度超過k的W是Xm-1和Y的LCS,那W肯定也X和Y的公共子串,而已知條件中X和Y的公共子串的最大長度爲k。矛盾。
3.       證明同2。

 

有了上面的性質,我們可以得出如下的思路:

求兩字符串Xm={x0, x1,…xm-1}和Yn={y0,y1,…,yn-1}的LCS,如果xm-1=yn-1,那麼只需求得Xm-1和Yn-1的LCS,並在其後添加xm-1(yn-1)即可;如果xm-1≠yn-1,我們分別求得Xm-1和Y的LCS和Yn-1和X的LCS,並且這兩個LCS中較長的一個爲X和Y的LCS。

 

如果我們記字符串Xi和Yj的LCS的長度爲c[i,j],我們可以遞歸地求c[i,j]:

          /      0                               if i<0 or j<0
c[i,j]=          c[i-1,j-1]+1                    if i,j>=0 and xi=xj
         /       max(c[i,j-1],c[i-1,j]           if i,j>=0 and xi≠xj

 

上面的公式用遞歸函數不難求得。但從前面求Fibonacci第n項(本微軟等100題系列第19題)的分析中我們知道直接遞歸會有很多重複計算,我們用從底向上循環求解的思路效率更高。

 

爲了能夠採用循環求解的思路,我們用一個矩陣(參考代碼中的LCS_length)保存下來當前已經計算好了的c[i,j],當後面的計算需要這些數據時就可以直接從矩陣讀取。另外,求取c[i,j]可以從c[i-1,j-1] 、c[i,j-1]或者c[i-1,j]三個方向計算得到,相當於在矩陣LCS_length中是從c[i-1,j-1],c[i,j-1]或者c[i-1,j]的某一個各自移動到c[i,j],因此在矩陣中有三種不同的移動方向:向左、向上和向左上方,其中只有向左上方移動時才表明找到LCS中的一個字符。於是我們需要用另外一個矩陣(參考代碼中的LCS_direction)保存移動的方向。

 

參考代碼如下:
#include "string.h"

// directions of LCS generation
enum decreaseDir {kInit = 0, kLeft, kUp, kLeftUp};

/////////////////////////////////////////////////////////////////////////////
// Get the length of two strings' LCSs, and print one of the LCSs
// Input: pStr1         - the first string
//        pStr2         - the second string
// Output: the length of two strings' LCSs
/////////////////////////////////////////////////////////////////////////////
int LCS(char* pStr1, char* pStr2)
{
      if(!pStr1 || !pStr2)
            return 0;

      size_t length1 = strlen(pStr1);
      size_t length2 = strlen(pStr2);
      if(!length1 || !length2)
            return 0;

      size_t i, j;

      // initiate the length matrix
      int **LCS_length;
      LCS_length = (int**)(new int[length1]);
      for(i = 0; i < length1; ++ i)
            LCS_length[i] = (int*)new int[length2];

      for(i = 0; i < length1; ++ i)
            for(j = 0; j < length2; ++ j)
                  LCS_length[i][j] = 0;

 

      // initiate the direction matrix
      int **LCS_direction;
      LCS_direction = (int**)(new int[length1]);
      for( i = 0; i < length1; ++ i)
            LCS_direction[i] = (int*)new int[length2];

      for(i = 0; i < length1; ++ i)
            for(j = 0; j < length2; ++ j)
                  LCS_direction[i][j] = kInit;

      for(i = 0; i < length1; ++ i)
      {
            for(j = 0; j < length2; ++ j)
            {
                  if(i == 0 || j == 0)
                  {
                        if(pStr1[i] == pStr2[j])
                        {
                              LCS_length[i][j] = 1;
                              LCS_direction[i][j] = kLeftUp;
                        }
                        else
                              LCS_length[i][j] = 0;
                  }
                  // a char of LCS is found,
                  // it comes from the left up entry in the direction matrix
                  else if(pStr1[i] == pStr2[j])
                  {
                        LCS_length[i][j] = LCS_length[i - 1][j - 1] + 1;
                        LCS_direction[i][j] = kLeftUp;
                  }
                  // it comes from the up entry in the direction matrix
                  else if(LCS_length[i - 1][j] > LCS_length[i][j - 1])
                  {
                        LCS_length[i][j] = LCS_length[i - 1][j];
                        LCS_direction[i][j] = kUp;
                  }
                  // it comes from the left entry in the direction matrix
                  else
                  {
                        LCS_length[i][j] = LCS_length[i][j - 1];
                        LCS_direction[i][j] = kLeft;
                  }
            }
      }
      LCS_Print(LCS_direction, pStr1, pStr2, length1 - 1, length2 - 1);

      return LCS_length[length1 - 1][length2 - 1];
}

 

/////////////////////////////////////////////////////////////////////////////
// Print a LCS for two strings
// Input: LCS_direction - a 2d matrix which records the direction of
//                        LCS generation
//        pStr1         - the first string
//        pStr2         - the second string
//        row           - the row index in the matrix LCS_direction
//        col           - the column index in the matrix LCS_direction
/////////////////////////////////////////////////////////////////////////////
void LCS_Print(int **LCS_direction,
                    char* pStr1, char* pStr2,
                    size_t row, size_t col)
{
      if(pStr1 == NULL || pStr2 == NULL)
            return;

      size_t length1 = strlen(pStr1);
      size_t length2 = strlen(pStr2);

      if(length1 == 0 || length2 == 0 || !(row < length1 && col < length2))
            return;

      // kLeftUp implies a char in the LCS is found
      if(LCS_direction[row][col] == kLeftUp)
      {
            if(row > 0 && col > 0)
                  LCS_Print(LCS_direction, pStr1, pStr2, row - 1, col - 1);

            // print the char
            printf("%c", pStr1[row]);
      }
      else if(LCS_direction[row][col] == kLeft)
      {
            // move to the left entry in the direction matrix
            if(col > 0)
                  LCS_Print(LCS_direction, pStr1, pStr2, row, col - 1);
      }
      else if(LCS_direction[row][col] == kUp)
      {
            // move to the up entry in the direction matrix
            if(row > 0)
                  LCS_Print(LCS_direction, pStr1, pStr2, row - 1, col);
      }
}
擴展:如果題目改成求兩個字符串的最長公共子字符串,應該怎麼求?子字符串的定義和子串的定義類似,但要求是連續分佈在其他字符串中。比如輸入兩個字符串BDCABA和ABCBDAB的最長公共字符串有BD和AB,它們的長度都是2。

 //July注,更多可參考 算法導論一書第15章 動態規劃問題。
 //及我針對此題寫的一篇博文:24個經典算法系列:3、動態規劃算法解一道面試題
 //http://blog.csdn.net/v_JULY_v/archive/2010/12/31/6110269.aspx
 //關於動態規劃算法,我日後還會在博客裏清晰闡述。:D。
 


57.用倆個棧實現隊列。
題目:某隊列的聲明如下:
template<typename T> class CQueue
{
public:
      CQueue() {}
      ~CQueue() {}

      void appendTail(const T& node);  // append a element to tail
      void deleteHead();               // remove a element from head

private:
     T> m_stack1;
     T> m_stack2;
};

分析:從上面的類的聲明中,我們發現在隊列中有兩個棧。
因此這道題實質上是要求我們用兩個棧來實現一個隊列。
相信大家對棧和隊列的基本性質都非常瞭解了:棧是一種後入先出的數據容器,
因此對隊列進行的插入和刪除操作都是在棧頂上進行;隊列是一種先入先出的數據容器,
我們總是把新元素插入到隊列的尾部,而從隊列的頭部刪除元素。
參考代碼如下:
// Append a element at the tail of the queue
template<typename T> void CQueue<T>::appendTail(const T& element)
{
      // push the new element into m_stack1
      m_stack1.push(element);
}

// Delete the head from the queue
template<typename T> void CQueue<T>::deleteHead()
{
      // if m_stack2 is empty, and there are some
      // elements in m_stack1, push them in m_stack2
      if(m_stack2.size() <= 0)
      {
            while(m_stack1.size() > 0)
            {
                  T& data = m_stack1.top();
                  m_stack1.pop();
                  m_stack2.push(data);
            }
      }

      // push the element into m_stack2
      assert(m_stack2.size() > 0);
      m_stack2.pop();
}

擴展:這道題是用兩個棧實現一個隊列。反過來能不能用兩個隊列實現一個棧?如果可以,該如何實現?

 


58.從尾到頭輸出鏈表。
題目:輸入一個鏈表的頭結點,從尾到頭反過來輸出每個結點的值。鏈表結點定義如下:
struct ListNode
{

      int       m_nKey;
      ListNode* m_pNext;
};
分析:這是一道很有意思的面試題。
該題以及它的變體經常出現在各大公司的面試、筆試題中。

 

看到這道題後,第一反應是從頭到尾輸出比較簡單。於是很自然地想到把鏈表中鏈接結點的指針反轉過來,改變鏈表的方向。然後就可以從頭到尾輸出了。反轉鏈表的算法詳見本人面試題精選系列的第19題,在此不再細述。但該方法需要額外的操作,應該還有更好的方法。

 

接下來的想法是從頭到尾遍歷鏈表,每經過一個結點的時候,把該結點放到一個棧中。當遍歷完整個鏈表後,再從棧頂開始輸出結點的值,此時輸出的結點的順序已經反轉過來了。該方法需要維護一個額外的棧,實現起來比較麻煩。

既然想到了棧來實現這個函數,而遞歸本質上就是一個棧結構。於是很自然的又想到了用遞歸來實現。要實現反過來輸出鏈表,我們每訪問到一個結點的時候,先遞歸輸出它後面的結點,再輸出該結點自身,這樣鏈表的輸出結果就反過來了。

 

基於這樣的思路,不難寫出如下代碼:
// Print a list from end to beginning
// Input: pListHead - the head of list
void PrintListReversely(ListNode* pListHead)
{
      if(pListHead != NULL)
      {
            // Print the next node first
            if (pListHead->m_pNext != NULL)
            {
                  PrintListReversely(pListHead->m_pNext);
            }

            // Print this node
            printf("%d", pListHead->m_nKey);
      }
}
擴展:該題還有兩個常見的變體:
1.       從尾到頭輸出一個字符串;
2.       定義一個函數求字符串的長度,要求該函數體內不能聲明任何變量。

 


59.不能被繼承的類。
題目:用C++設計一個不能被繼承的類。

分析:這是Adobe公司2007年校園招聘的最新筆試題。


這道題除了考察應聘者的C++基本功底外,還能考察反應能力,是一道很好的題目。
在Java中定義了關鍵字final,被final修飾的類不能被繼承。但在C++中沒有final這個關鍵字,要實現這個要求還是需要花費一些精力。

 

首先想到的是在C++ 中,子類的構造函數會自動調用父類的構造函數。同樣,子類的析構函數也會自動調用父類的析構函數。要想一個類不能被繼承,我們只要把它的構造函數和析構函數都定義爲私有函數。那麼當一個類試圖從它那繼承的時候,必然會由於試圖調用構造函數、析構函數而導致編譯錯誤。

 

可是這個類的構造函數和析構函數都是私有函數了,我們怎樣才能得到該類的實例呢?

這難不倒我們,我們可以通過定義靜態來創建和釋放類的實例。

 

基於這個思路,我們可以寫出如下的代碼:
// Define a class which can't be derived from
class FinalClass1
{
public:
      static FinalClass1* GetInstance()
      {
            return new FinalClass1;
      }
      static void DeleteInstance( FinalClass1* pInstance)
      {
            delete pInstance;
            pInstance = 0;
      }
private:
      FinalClass1() {}
      ~FinalClass1() {}
};

 

這個類是不能被繼承,但在總覺得它和一般的類有些不一樣,使用起來也有點不方便。比如,我們只能得到位於堆上的實例,而得不到位於棧上實例。

 

能不能實現一個和一般類除了不能被繼承之外其他用法都一樣的類呢?辦法總是有的,不過需要一些技巧。請看如下代碼:

// Define a class which can't be derived from
template <typename T> class MakeFinal
{
      friend T;
private:
      MakeFinal() {}
      ~MakeFinal() {}
};

class FinalClass2 : virtual public MakeFinal<FinalClass2>
{
public:

      FinalClass2() {}
      ~FinalClass2() {}
};

這個類使用起來和一般的類沒有區別,可以在棧上、也可以在堆上創建實例。儘管類MakeFinal<FinalClass2>的構造函數和析構函數都是私有的,但由於類FinalClass2是它的友元函數,因此在FinalClass2中調用MakeFinal<FinalClass2>的構造函數和析構函數都不會造成編譯錯誤。

 

但當我們試圖從FinalClass2繼承一個類並創建它的實例時,卻不同通過編譯。
class Try : public FinalClass2
{
public:
      Try() {}
      ~Try() {}
};

Try temp;
由於類FinalClass2是從類MakeFinal<FinalClass2>虛繼承過來的,在調用Try的構造函數的時候,會直接跳過FinalClass2而直接調用MakeFinal<FinalClass2>的構造函數。非常遺憾的是,Try不是MakeFinal<FinalClass2>的友元,因此不能調用其私有的構造函數。

基於上面的分析,試圖從FinalClass2繼承的類,一旦實例化,都會導致編譯錯誤,因此是FinalClass2不能被繼承。這就滿足了我們設計要求。
 

 

60.在O(1)時間內刪除鏈表結點。
題目:給定鏈表的頭指針和一個結點指針,在O(1)時間刪除該結點。鏈表結點的定義如下:
struct ListNode

{

      int        m_nKey;

      ListNode*  m_pNext;

};


函數的聲明如下:
void DeleteNode(ListNode* pListHead, ListNode* pToBeDeleted);

分析:這是一道廣爲流傳的Google面試題,能有效考察我們的編程基本功,還能考察我們的反應速度,
更重要的是,還能考察我們對時間複雜度的理解。
在鏈表中刪除一個結點,最常規的做法是從鏈表的頭結點開始,順序查找要刪除的結點,找到之後再刪除。由於需要順序查找,時間複雜度自然就是O(n) 了。

 

我們之所以需要從頭結點開始查找要刪除的結點,是因爲我們需要得到要刪除的結點的前面一個結點。我們試着換一種思路。我們可以從給定的結點得到它的下一個結點。這個時候我們實際刪除的是它的下一個結點,由於我們已經得到實際刪除的結點的前面一個結點,因此完全是可以實現的。當然,在刪除之前,我們需要需要把給定的結點的下一個結點的數據拷貝到給定的結點中。此時,時間複雜度爲O(1)。

 

上面的思路還有一個問題:如果刪除的結點位於鏈表的尾部,沒有下一個結點,怎麼辦?我們仍然從鏈表的頭結點開始,順便遍歷得到給定結點的前序結點,並完成刪除操作。這個時候時間複雜度是O(n)。

那題目要求我們需要在O(1)時間完成刪除操作,我們的算法是不是不符合要求?實際上,假設鏈表總共有n個結點,我們的算法在n-1總情況下時間複雜度是O(1),只有當給定的結點處於鏈表末尾的時候,時間複雜度爲O(n)。那麼平均時間複雜度[(n-1)*O(1)+O(n)]/n,仍然爲O(1)。

 

基於前面的分析,我們不難寫出下面的代碼。
參考代碼:
// Delete a node in a list
// Input: pListHead - the head of list
//        pToBeDeleted - the node to be deleted
void DeleteNode(ListNode* pListHead, ListNode* pToBeDeleted)
{
      if(!pListHead || !pToBeDeleted)
            return;

      // if pToBeDeleted is not the last node in the list
      if(pToBeDeleted->m_pNext != NULL)
      {
            // copy data from the node next to pToBeDeleted
            ListNode* pNext = pToBeDeleted->m_pNext;
            pToBeDeleted->m_nKey = pNext->m_nKey;
            pToBeDeleted->m_pNext = pNext->m_pNext;
 
            // delete the node next to the pToBeDeleted
            delete pNext;
            pNext = NULL;
      }
      // if pToBeDeleted is the last node in the list
      else
      {
            // get the node prior to pToBeDeleted
            ListNode* pNode = pListHead;
            while(pNode->m_pNext != pToBeDeleted)
            {
                  pNode = pNode->m_pNext;            
            }
 
            // deleted pToBeDeleted
            pNode->m_pNext = NULL;
            delete pToBeDeleted;
            pToBeDeleted = NULL;
      }
}

值得注意的是,爲了讓代碼看起來簡潔一些,上面的代碼基於兩個假設:

(1)給定的結點的確在鏈表中;(2)給定的要刪除的結點不是鏈表的頭結點。

不考慮第一個假設對代碼的魯棒性是有影響的。至於第二個假設,當整個列表只有一個結點時,代碼會有問題。但這個假設不算很過分,因爲在有些鏈表的實現中,會創建一個虛擬的鏈表頭,並不是一個實際的鏈表結點。這樣要刪除的結點就不可能是鏈表的頭結點了。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章