資料結構面試大全

cklea發表於2008-03-09
摘要: 1.判斷連結串列是否存在環型連結串列問題:判斷一個連結串列是否存在環,例如下面這個連結串列就存在一個環:例如N1->N2->N3->N4- >N5->N2就是一個有環的連結串列,環的開始結點是N5這裡有一個比較簡單的解法。設定兩個指標p1,p2。每次迴圈p1向前走一步,p2向前走兩步。直到p2碰到NULL指標或者兩個指標相等結束迴圈。如果兩個指標相等則說明存在環。[@more@]
1.判斷連結串列是否存在環型連結串列問題:判斷一個連結串列是否存在環,例如下面這個連結串列就存在一個環:例如N1->N2->N3->N4->N5->N2就是一個有環的連結串列,環的開始結點是N5這裡有一個比較簡單的解法。設定兩個指標p1p2。每次迴圈p1向前走一步,p2向前走兩步。直到p2碰到NULL指標或者兩個指標相等結束迴圈。如果兩個指標相等則說明存在環。
struct link 
{
   int data;
    link* next;
};
 
bool IsLoop(link* head)
{
    link* p1=head, *p2 = head;
     if (head ==NULL || head->next ==NULL) 
     {
          return false;
     }
    do{
        p1= p1->next;
        p2 = p2->next->next;
    } while(p2 && p2->next && p1!=p2);     
     if(p1 == p2)
          return true;
     else
          return false;
}
2,連結串列反轉
單向連結串列的反轉是一個經常被問到的一個面試題,也是一個非常基礎的問題。比如一個連結串列是這樣的: 1->2->3->4->5 透過反轉後成為5->4->3->2->1。最容易想到的方法遍歷一遍連結串列,利用一個輔助指標,儲存遍歷過程中當前指標指向的下一個元素,然後將當前節點元素的指標反轉後,利用已經儲存的指標往後面繼續遍歷。原始碼如下:
struct linka {
     int data;
     linka* next;
};
 
void reverse(linka*& head)
{
     if(head ==NULL)
          return;
     linka*pre, *cur, *ne;
     pre=head;
     cur=head->next;
     while(cur)
     {
          ne = cur->next;
          cur->next = pre;
          pre = cur;
          cur = ne;
     }
     head->next = NULL;
     head = pre;
}
還有一種利用遞迴的方法。這種方法的基本思想是在反轉當前節點之前先呼叫遞迴函式反轉後續節點。原始碼如下。不過這個方法有一個缺點,就是在反轉後的最後一個結點會形成一個環,所以必須將函式的返回的節點的next域置為NULL。因為要改變head指標,所以我用了引用。演算法的原始碼如下:
linka* reverse(linka* p,linka*& head)
{
     if(p == NULL || p->next == NULL)
     {
          head=p;
          return p;
     }
     else
     {
          linka* tmp = reverse(p->next,head);
          tmp->next = p;
          return p;
     }
}
3,判斷兩個陣列中是否存在相同的數字
給定兩個排好序的陣列,怎樣高效得判斷這兩個陣列中存在相同的數字?這個問題首先想到的是一個O(nlogn)的演算法。就是任意挑選一個陣列,遍歷這個陣列的所有元素,遍歷過程中,在另一個陣列中對第一個陣列中的每個元素進行binary search。用C++實現程式碼如下:
bool findcommon(int a[],int size1,int b[],int size2)
{
     int i;
     for(i=0;i
     {
          int start=0,end=size2-1,mid;
          while(start<=end)
          {
               mid=(start+end)/2;
               if(a[i]==b[mid])
                    return true;
               else if (a[i]
                    end=mid-1;
               else
                    start=mid+1;
          }
     }
     return false;
}
後來發現有一個 O(n)演算法。因為兩個陣列都是排好序的。所以只要一次遍歷就行了。首先設兩個下標,分別初始化為兩個陣列的起始地址,依次向前推進。推進的規則是比較兩個陣列中的數字,小的那個陣列的下標向前推進一步,直到任何一個陣列的下標到達陣列末尾時,如果這時還沒碰到相同的數字,說明陣列中沒有相同的數字。
bool findcommon2(int a[], int size1, int b[], int size2)
{
     int i=0,j=0;
     while(i
     {
          if(a[i]==b[j])
               return true;
          if(a[i]>b[j])
               j++;
          if(a[i]
               i++;
     }
     return false;
}
4,最大子序列
問題:給定一整數序列A1 A2... An (可能有負數),求A1~An的一個子序列Ai~Aj,使得AiAj的和最大例如:整數序列-2, 11, -4, 13, -5, 2, -5, -3, 12, -9的最大子序列的和為21對於這個問題,最簡單也是最容易想到的那就是窮舉所有子序列的方法。利用三重迴圈,依次求出所有子序列的和然後取最大的那個。當然演算法複雜度會達到O(n^3)。顯然這種方法不是最優的,下面給出一個演算法複雜度為O(n)的線性演算法實現,演算法的來源於一書。
在給出線性演算法之前,先來看一個對窮舉演算法進行最佳化的演算法,它的演算法複雜度為O(n^2)。其實這個演算法只是對對窮舉演算法稍微做了一些修改:其實子序列的和我們並不需要每次都重新計算一遍。假設Sum(i, j)A[i] ... A[j]的和,那麼Sum(i, j+1) = Sum(i, j) + A[j+1]。利用這一個遞推,我們就可以得到下面這個演算法:
int max_sub(int a[],int size)
{
     int i,j,v,max=a[0];
     for(i=0;i
     {
          v=0;
          for(j=i;j
          {
               v=v+a[j];//Sum(i, j+1) = Sum(i, j) + A[j+1]
               if(v>max)
                    max=v;
          }
     }
     return max;
}
那怎樣才能達到線性複雜度呢?這裡運用動態規劃的思想。先看一下原始碼實現:
int max_sub2(int a[], int size)
{
     int i,max=0,temp_sum=0;
     for(i=0;i
     {
          temp_sum+=a[i];
          if(temp_sum>max)
               max=temp_sum;
          else if(temp_sum<0)
               temp_sum=0;
     }
     return max;
}
在這一遍掃描陣列當中,從左到右記錄當前子序列的和temp_sum,若這個和不斷增加,那麼最大子序列的和max也不斷增加(不斷更新max)。如果往前掃描中遇到負數,那麼當前子序列的和將會減小。此時temp_sum 將會小於max,當然max也就不更新。如果temp_sum降到0時,說明前面已經掃描的那一段就可以拋棄了,這時將temp_sum置為0。然後,temp_sum將從後面開始將這個子段進行分析,若有比當前max大的子段,繼續更新max。這樣一趟掃描結果也就出來了。5, 找出單向連結串列的中間結點
這道題和解判斷連結串列是否存在環,我用的是非常類似的方法,只不過結束迴圈的條件和函式返回值不一樣罷了。設定兩個指標p1p2。每次迴圈p1向前走一步,p2向前走兩步。當p2到達連結串列的末尾時,p1指向的時連結串列的中間。
link* mid(link* head)
{
       link* p1,*p2;
       p1=p2=head;
       if(head==NULL || head->next==NULL)
              return head;
       do {
              p1=p1->next;
              p2=p2->next->next;
       } while(p2 && p2->next);
       return p1;
}
6,按單詞反轉字串
並不是簡單的字串反轉,而是按給定字串裡的單詞將字串倒轉過來,就是說字串裡面的單詞還是保持原來的順序,這裡的每個單詞用空格分開。例如:
Here is
經過反轉後變為:
is Here
如果只是簡單的將所有字串翻轉的話,可以遍歷字串,將第一個字元和最後一個交換,第二個和倒數第二個交換,依次迴圈。其實按照單詞反轉的話可以在第一遍遍歷的基礎上,再遍歷一遍字串,對每一個單詞再反轉一次。這樣每個單詞又恢復了原來的順序。
char* reverse_word(const char* str)
{
     int len = strlen(str);
     char* restr = new char[len+1];
     strcpy(restr,str);
     int i,j;
     for(i=0,j=len-1;i
     {
          char temp=restr[i];
          restr[i]=restr[j];
          restr[j]=temp;
     }
     int k=0;
     while(k
     {
          i=j=k;
          while(restr[j]!=' ' && restr[j]!='' )
               j++;
          k=j+1;
          j--;
          for(;i
          {
               char temp=restr[i];
               restr[i]=restr[j];
               restr[j]=temp;
          }
     }
     return restr;
}
如果考慮空間和時間的最佳化的話,當然可以將上面程式碼裡兩個字串交換部分改為異或實現。例如將
          char temp=restr[i];
          restr[i]=restr[j];
          restr[j]=temp;
改為
          restr[i]^=restr[j];
          restr[j]^=restr[i];
          restr[i]^=restr[j];
 
7,字串反轉
我沒有記錯的話是一道MSN的筆試題,網上無意中看到的,拿來做了一下。題目是這樣的,給定一個字串,一個這個字串的子串,將第一個字串反轉,但保留子串的順序不變。例如:輸入:第一個字串: "This is fishsky 's Chinese site: 子串: "fishsky"輸出: "nc/nc.moc.fishsky.www//:ptth :etis esenihC s'fishsky si sihT"一般的方法是先掃描一邊第一個字串,然後用stack把它反轉,同時記錄下子串出現的位置。然後再掃描一遍把記錄下來的子串再用stack反轉。我用的方法是用一遍掃描陣列的方法。掃描中如果發現子串,就將子串倒過來壓入堆疊。最後再將堆疊裡的字元彈出,這樣子串又恢復了原來的順序。原始碼如下:
#include 
#include 
#include 
using namespace std;
//reverse the string 's1' except the substring 'token'.
const char* reverse(const char* s1, const char* token)
{
       assert(s1 && token);
       stack stack1;
       const char* ptoken = token, *head = s1, *rear = s1;
       while (*head != '')
       {
              while(*head!= '' && *ptoken == *head)
              {
                 ptoken++;
                 head++;
              }
              if(*ptoken == '')//contain the token
              {
                 const char* p;
                 for(p=head-1;p>=rear;p--)
                        stack1.push(*p);
 
                 ptoken = token;
                 rear = head;
              }
              else
                    

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/7196059/viewspace-1000617/,如需轉載,請註明出處,否則將追究法律責任。

上一篇: 關於過載(ZT)
資料結構面試大全
請登入後發表評論 登入
全部評論

相關文章