两个指针之美

无意中看见了这篇帖子。感觉写的还不错。尤其是求链表中倒数第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. }  

这道题有很多变种。这里要求是把奇数放在偶数的前面,如果把要求改成:把负数放在非负数的前面等,思路都是都一样的

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