兩個指針之美

無意中看見了這篇帖子。感覺寫的還不錯。尤其是求鏈表中倒數第K個節點,其他技巧都是一些比較常見的思路了,不過也能帶給我更多的思考。

源:http://blog.csdn.net/dlengong/article/details/7418420

使用兩個指針可以輕鬆的解決許多算法問題,歸納出如下幾種

1、  判斷鏈表是否帶環

帶環鏈表的判斷是鏈表中經常考察的內容。一個循環鏈表可以無休止地遍歷下去。我們可以定義兩個指針,一個快指針一個慢指針,如果在遍歷到尾端前二者相遇,那麼鏈表就是有環鏈表

  1. bool haveCycle(LinkList * Head)  
  2. {  
  3.     if (!Head)  
  4.     {  
  5.         return false;  
  6.     }  
  7.     LinkList * fast=Head;  
  8.     LinkList * slow=Head;  
  9.     while (!fast)  
  10.     {  
  11.         if (fast==slow) //如果兩指針相遇,則返回真  
  12.         {  
  13.             return true;  
  14.         }  
  15.         fast=fast->next;  
  16.         if (!fast)  
  17.         {  
  18.             return false;  
  19.         }  
  20.         fast=fast->next;   //快指針走兩步  
  21.         slow=slow->next;   //慢指針走一步  
  22.     }  
  23.     return false;  
  24. }  


 

2、  求鏈表中倒數第K個節點

如果用普通的思路得遍歷兩遍鏈表,第一遍先求出鏈表的總長度N,然後第二遍走到第N-k個節點,這個節點就是所求的節點。如果鏈表很長,那麼遍歷兩次的話就很費時,我們用兩個指針的方法遍歷一次鏈表即可,先讓一個指針走K步,然後兩個指針再一起走,直到第一個指針遍歷到鏈表末尾。

  1. LinkList * FindRevK(LinkList * Head,int k)  
  2. {  
  3.     if (!Head)  
  4.     {  
  5.         return NULL;  
  6.     }  
  7.     LinkList * first=Head;  
  8.     LinkList * second=Head;  
  9.     for (int i=0;i<k;i++)  //先讓first走K步  
  10.     {  
  11.         if (!first)  
  12.         {  
  13.             return NULL; //鏈表長度小於K則返回NULL  
  14.         }  
  15.         first=first->next;  
  16.     }  
  17.     while (first)  
  18.     {  
  19.         first=first->next;  
  20.         second=second->next;  
  21.     }  
  22.     return second;  
  23. }  


 

3、  二分法查找某個數

用經典的二分法在一個有序數組中可以以log(n)的時間複雜度查找出給定的數。同樣也是設定兩個位置下表,lowhigh。由於該方法大家都熟悉不過了,只把代碼貼上來就行了

  1. int search(int array[],int n,int value)  
  2. {  
  3.     if (!array||n<0)  
  4.     {  
  5.         return -1;  
  6.     }  
  7.     int low=0;  
  8.     int high=n-1;     
  9.     while (low<high)  
  10.     {  
  11.         int med=low+(high-low)/2; //這樣可以防止low+high發生溢出  
  12.         if (array[med]<value)  
  13.         {  
  14.             low=med+1;  
  15.         }  
  16.         else if (array[med]>value)  
  17.         {  
  18.             high=med-1;  
  19.         }  
  20.         else  
  21.             return med;  
  22.     }  
  23.     return -1;  
  24. }  

 

4、  在一個有序數組中找出和爲N的兩個數

定義兩個位置lowhigh,一個在開始處,一個在結尾處,如果二者之和大於Nhigh遞減,如果二者之和小於Nlow遞增,直到和爲N或者二者相遇爲止

  1. void findNum(int * array,int arraysize,int N,int & low,int & high)  
  2. {  
  3.     low=0;  
  4.     high=arraysize-1;  
  5.     while (low<high)  
  6.     {  
  7.         if (low+high==N)  
  8.         {  
  9.             return;  //和爲N,返回  
  10.         }  
  11.         else if (low+high<N)  
  12.         {  
  13.             low++;  
  14.         }  
  15.         else  
  16.         {  
  17.             high--;  
  18.         }  
  19.     }  
  20.     //沒有兩個數的和爲N,則置下表爲-1  
  21.     low=-1;  
  22.     high=-1;  
  23. }  

5、  輸入一個正數n,輸出所有和爲n連續正數序列

輸入 15
輸出
15=1+2+3+4+5
15=4+5+6
15=7+8

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

[java] view plaincopy
  1. void printResult(int low,int high,int num)  //該函數實現將low到high之間的數輸出到屏幕上  
  2. {  
  3.     cout<<num<<"=";  
  4.     for (int i=low;i<high;i++)  
  5.     {  
  6.         cout<<i<<"+";  
  7.     }  
  8.     cout<<high<<endl;  
  9. }  
  10. void consecutiveN(int num)  
  11. {  
  12.     int low=1,high=2;  
  13.     int sum=low+high;  
  14.     while (low<(num+1)/2)  
  15.     {  
  16.         if (sum==num)  
  17.         {  
  18.             printResult(low,high,num); //輸出結果  
  19.             sum-=low;  
  20.             low++;  
  21.         }  
  22.         while(sum<num)  
  23.         {  
  24.             high++;  
  25.             sum+=high;  
  26.         }  
  27.         while(sum>num)  
  28.         {  
  29.             sum-=low;  
  30.             low++;  
  31.         }  
  32.     }  
  33. }  

6、 輸入一個整數數組,調整數組中數字的順序,使得所有奇數位於數組的前半部分,所有偶數位於數組的後半部分

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

要求的是把奇數放在數組的前半部分,偶數放在數組的後半部分,因此所有的奇數應該位於偶數的前面。也就是說我們在掃描這個數組的時候,如果發現有偶數出現在奇數的前面,我們可以交換他們的順序,交換之後就符合要求了。因此我們可以維護兩個指針,第一個指針初始化爲數組的第一個數字,它只向後移動;第二個指針初始化爲數組的最後一個數字,它只向前移動。在兩個指針相遇之前,第一個指針總是位於第二個指針的前面。如果第一個指針指向的數字是偶數而第二個指針指向的數字是奇數,我們就交換這兩個數字

  1. void Reorder(int *pData, unsigned int length, bool (*func)(int));  
  2. bool isEven(int n)  
  3. {  
  4.     return (n & 1) == 0; //判斷一個數字是不是偶數並沒有用%運算符而是用&。理由是通常情況下位運算符比%要快一些  
  5. }  
  6. void ReorderOddEven(int *pData, unsigned int length)  
  7. {  
  8.     if(pData == NULL || length == 0)  
  9.         return;   
  10.     Reorder(pData, length, isEven);  
  11. }  
  12. void Reorder(int *pData, unsigned int length, bool (*func)(int))  
  13. {  
  14.     if(pData == NULL || length == 0)  
  15.         return;   
  16.     int *pBegin = pData;  
  17.     int *pEnd = pData + length - 1;   
  18.     while(pBegin < pEnd)  
  19.     {  
  20.         if(!func(*pBegin)) //從前半部分找出第一個偶數  
  21.         {  
  22.             pBegin ++;  
  23.             continue;  
  24.         }  
  25.         if(func(*pEnd))  //從後半部分找出第一個奇數  
  26.         {  
  27.             pEnd --;  
  28.             continue;  
  29.         }  
  30.         //二者交換  
  31.         int temp = *pBegin;  
  32.         *pBegin = *pEnd;  
  33.         *pEnd = temp;  
  34.     }  
  35. }  

這道題有很多變種。這裏要求是把奇數放在偶數的前面,如果把要求改成:把負數放在非負數的前面等,思路都是都一樣的

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