兩個指針之美(筆試)
轉自:http://blog.163.com/linux___/blog/static/20643000520124194322107/
使用兩個指針可以輕鬆的解決許多算法問題,歸納出如下幾種
1、 判斷鏈表是否帶環
帶環鏈表的判斷是鏈表中經常考察的內容。一個循環鏈表可以無休止地遍歷下去。我們可以定義兩個指針,一個快指針一個慢指針,如果在遍歷到尾端前二者相遇,那麼鏈表就是有環鏈表
- bool haveCycle(LinkList * Head)
- {
- if (!Head)
- {
- return false;
- }
- LinkList * fast=Head;
- LinkList * slow=Head;
- while (!fast)
- {
- if (fast==slow) //如果兩指針相遇,則返回真
- {
- return true;
- }
- fast=fast->next;
- if (!fast)
- {
- return false;
- }
- fast=fast->next; //快指針走兩步
- slow=slow->next; //慢指針走一步
- }
- return false;
- }
2、 求鏈表中倒數第K個節點
如果用普通的思路得遍歷兩遍鏈表,第一遍先求出鏈表的總長度N,然後第二遍走到第N-k個節點,這個節點就是所求的節點。如果鏈表很長,那麼遍歷兩次的話就很費時,我們用兩個指針的方法遍歷一次鏈表即可,先讓一個指針走K步,然後兩個指針再一起走,直到第一個指針遍歷到鏈表末尾。
- LinkList * FindRevK(LinkList * Head,int k)
- {
- if (!Head)
- {
- return NULL;
- }
- LinkList * first=Head;
- LinkList * second=Head;
- for (int i=0;i<k;i++) //先讓first走K步
- {
- if (!first)
- {
- return NULL; //鏈表長度小於K則返回NULL
- }
- first=first->next;
- }
- while (first)
- {
- first=first->next;
- second=second->next;
- }
- return second;
- }
3、 二分法查找某個數
用經典的二分法在一個有序數組中可以以log(n)的時間複雜度查找出給定的數。同樣也是設定兩個位置下表,low與high。由於該方法大家都熟悉不過了,只把代碼貼上來就行了
- int search(int array[],int n,int value)
- {
- if (!array||n<0)
- {
- return -1;
- }
- int low=0;
- int high=n-1;
- while (low<high)
- {
- int med=low+(high-low)/2; //這樣可以防止low+high發生溢出
- if (array[med]<value)
- {
- low=med+1;
- }
- else if (array[med]>value)
- {
- high=med-1;
- }
- else
- return med;
- }
- return -1;
- }
4、 在一個有序數組中找出和爲N的兩個數
定義兩個位置low和high,一個在開始處,一個在結尾處,如果二者之和大於N,high遞減,如果二者之和小於N則low遞增,直到和爲N或者二者相遇爲止
- void findNum(int * array,int arraysize,int N,int & low,int & high)
- {
- low=0;
- high=arraysize-1;
- while (low<high)
- {
- if (low+high==N)
- {
- return; //和爲N,返回
- }
- else if (low+high<N)
- {
- low++;
- }
- else
- {
- high--;
- }
- }
- //沒有兩個數的和爲N,則置下表爲-1
- low=-1;
- high=-1;
- }
5、 輸入一個正數n,輸出所有和爲n連續正數序列
輸入 15
輸出
15=1+2+3+4+5
15=4+5+6
15=7+8
我們可用兩個數low和high分別表示序列的最小值和最大值。首先把low初始化爲1,high初始化爲2。如果從low到high的序列的和大於n的話,我們向右移動low,相當於從序列中去掉較小的數字。如果從low到high的序列的和小於n的話,我們向右移動high,相當於向序列中添加high的下一個數字。一直到low等於(1+n)/2,因爲序列至少要有兩個數字
- void printResult(int low,int high,int num) //該函數實現將low到high之間的數輸出到屏幕上
- {
- cout<<num<<"=";
- for (int i=low;i<high;i++)
- {
- cout<<i<<"+";
- }
- cout<<high<<endl;
- }
- void consecutiveN(int num)
- {
- int low=1,high=2;
- int sum=low+high;
- while (low<(num+1)/2)
- {
- if (sum==num)
- {
- printResult(low,high,num);//輸出結果
- sum-=low;
- low++;
- }
- while(sum<num)
- {
- high++;
- sum+=high;
- }
- while(sum>num)
- {
- sum-=low;
- low++;
- }
- }
- }
6、 輸入一個整數數組,調整數組中數字的順序,使得所有奇數位於數組的前半部分,所有偶數位於數組的後半部分
如果不考慮時間複雜度,最簡單的思路應該是從頭掃描這個數組,每碰到一個偶數時,拿出這個數字,並把位於這個數字後面的所有數字往前挪動一位。挪完之後在數組的末尾有一個空位,這時把該偶數放入這個空位。由於碰到一個偶數,需要移動O(n)個數字,因此總的時間複雜度是O(n2)。
要求的是把奇數放在數組的前半部分,偶數放在數組的後半部分,因此所有的奇數應該位於偶數的前面。也就是說我們在掃描這個數組的時候,如果發現有偶數出現在奇數的前面,我們可以交換他們的順序,交換之後就符合要求了。因此我們可以維護兩個指針,第一個指針初始化爲數組的第一個數字,它只向後移動;第二個指針初始化爲數組的最後一個數字,它只向前移動。在兩個指針相遇之前,第一個指針總是位於第二個指針的前面。如果第一個指針指向的數字是偶數而第二個指針指向的數字是奇數,我們就交換這兩個數字
- void Reorder(int *pData, unsigned int length,bool (*func)(int));
- bool isEven(int n)
- {
- return (n & 1) == 0; //判斷一個數字是不是偶數並沒有用%運算符而是用&。理由是通常情況下位運算符比%要快一些
- }
- void ReorderOddEven(int *pData, unsigned int length)
- {
- if(pData == NULL || length == 0)
- return;
- Reorder(pData, length, isEven);
- }
- 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(!func(*pBegin)) //從前半部分找出第一個偶數
- {
- pBegin ++;
- continue;
- }
- if(func(*pEnd)) //從後半部分找出第一個奇數
- {
- pEnd --;
- continue;
- }
- //二者交換
- int temp = *pBegin;
- *pBegin = *pEnd;
- *pEnd = temp;
- }
- }
這道題有很多變種。這裏要求是把奇數放在偶數的前面,如果把要求改成:把負數放在非負數的前面等,思路都是都一樣的