題目:輸入一個鏈表,輸出該鏈表中倒數第K個結點。為了符合大多數人的習慣,本題從1開始計數,即鏈表的尾結點是倒數第1個結點。例如一個鏈表有6個結點,從頭結點開始它們的值依次是1、2、3、4、5、6。這個鏈表的倒數第3個結點是值為4的結點。
看到這道題目,最直觀的想法,就是先算出鏈表的長度n,然后倒數第k個結點就是順序的第(n-k+1)個數,不過這樣需要2次遍歷鏈表,如果要求只能遍歷鏈表一次,那么上述算法就不符合要求了。
那我們就使用第二種算法,設定兩個指針p1和p2,兩個指針剛開始都指向鏈表的第一個結點,然后讓p1指針先走(k-1)步,然后再讓兩個指針一起往后走,當p1指針指向鏈表最后一個結點的時候,p2指針剛好指向鏈表中的倒數第k個結點。
在寫代碼的時候需要考慮魯棒性,最好采用防御性編程,就是考慮在哪些地方會出錯,然后提前加上錯誤判斷,這樣避免因此錯誤輸入而導致程序崩潰。
下面首先給出代碼實例,然后再講解程序魯棒性:
View Code
#include<iostream> #include<stdlib.h> #include<stack> using namespace std; //鏈表結構 struct ListNode { int m_nValue; ListNode* m_pNext; }; //創建一個鏈表結點 ListNode* CreateListNode(int value) { ListNode *pNode=new ListNode(); pNode->m_nValue=value; pNode->m_pNext=NULL; return pNode; } //遍歷鏈表中的所有結點 void PrintList(ListNode* pHead) { ListNode *pNode=pHead; while(pNode!=NULL) { cout<<pNode->m_nValue<<" "; pNode=pNode->m_pNext; } cout<<endl; } //輸出鏈表中的某一結點的值 void PrintListNode(ListNode* pNode) { if(pNode == NULL) { printf("The node is NULL\n"); } else { printf("The key in node is %d.\n", pNode->m_nValue); } } //往鏈表末尾添加結點 /* 注意這里pHead是一個指向指針的指針,在主函數中一般傳遞的是引用。 因為如果要為鏈表添加結點,那么就會修改鏈表結構,所以必須傳遞引用才能夠保存修改后的結構。 */ void AddToTail(ListNode** pHead,int value) { ListNode* pNew=new ListNode();//新插入的結點 pNew->m_nValue=value; pNew->m_pNext=NULL; if(*pHead==NULL)//空鏈表 { *pHead=pNew; } else { ListNode* pNode=*pHead; while(pNode->m_pNext!=NULL) pNode=pNode->m_pNext; pNode->m_pNext=pNew; } } //非防御性編程,非法輸入會導致錯誤。 ListNode* KthNodeFromEnd(ListNode* pHead,int k) { ListNode* pNode=pHead;//當前結點 ListNode* pKthNode=pHead;// while(k-1>0) { pNode=pNode->m_pNext;//讓pNode先走k-1步 --k; } while(pNode->m_pNext!=NULL) { pNode=pNode->m_pNext; pKthNode=pKthNode->m_pNext; } return pKthNode; } //防御性編程,魯棒性更好 ListNode* KthNodeFromEnd2(ListNode* pHead,int k) { if(pHead==NULL||k==0) return NULL; ListNode* pNode=pHead;//當前結點 ListNode* pKthNode=pHead;// while(k-1>0) { if(pNode->m_pNext!=NULL) { pNode=pNode->m_pNext;//讓pNode先走k-1步 --k; } else return NULL; } while(pNode->m_pNext!=NULL) { pNode=pNode->m_pNext; pKthNode=pKthNode->m_pNext; } return pKthNode; } void main() { //創建結點 ListNode* pNode1=CreateListNode(1);//創建一個結點 PrintList(pNode1);//打印 //往鏈表中添加新結點 AddToTail(&pNode1,2);//為鏈表添加一個結點 AddToTail(&pNode1,3);//為鏈表添加一個結點 AddToTail(&pNode1,4);//為鏈表添加一個結點 AddToTail(&pNode1,5);//為鏈表添加一個結點 AddToTail(&pNode1,6);//為鏈表添加一個結點 AddToTail(&pNode1,7);//為鏈表添加一個結點 //打印鏈表 PrintList(pNode1);//打印 //反轉鏈表 ListNode* KthNode=KthNodeFromEnd2(pNode1,3); PrintListNode(KthNode); system("pause"); }
上述程序中,求倒數第k個結點的第一個方法:ListNode* KthNodeFromEnd(ListNode* pHead,int k)。
這個方法看似滿足了我們的題目要求,但是當我們仔細分析,發現有3中方法讓程序崩潰
- 輸入的pListHead為空指針,由於代碼會嘗試訪問空指針指向的內存,程序崩潰
- 輸入的以pListHead為頭結點的鏈表的結點少於k。這樣在while(k-1>0)的循環中,又會出現嘗試訪問空指針指向的內存。
- 輸入的參數k小於等於0。根據題意k的最小值應為1。
考慮上述因素,我們給出了第二個方法:ListNode* KthNodeFromEnd2(ListNode* pHead,int k)。
相關題目
1.求鏈表的中間結點。如果鏈表中結點總數為奇數,返回中間結點;如果結點總數是偶數,返回中間兩個結點的任意一個。為了解決這個問題,我們也可以定義兩個指針,同時從鏈表的頭結點出發,一個指針一次走一步,另一個指針一次走兩步。當走得快的指針走到鏈表的末尾時,走得慢的指針正好在鏈表的中間。
代碼實例如下:
View Code
#include<iostream> #include<stdlib.h> #include<stack> using namespace std; //鏈表結構 struct ListNode { int m_nValue; ListNode* m_pNext; }; //創建一個鏈表結點 ListNode* CreateListNode(int value) { ListNode *pNode=new ListNode(); pNode->m_nValue=value; pNode->m_pNext=NULL; return pNode; } //遍歷鏈表中的所有結點 void PrintList(ListNode* pHead) { ListNode *pNode=pHead; while(pNode!=NULL) { cout<<pNode->m_nValue<<" "; pNode=pNode->m_pNext; } cout<<endl; } //輸出鏈表中的某一結點的值 void PrintListNode(ListNode* pNode) { if(pNode == NULL) { printf("The node is NULL\n"); } else { printf("The key in node is %d.\n", pNode->m_nValue); } } //往鏈表末尾添加結點 /* 注意這里pHead是一個指向指針的指針,在主函數中一般傳遞的是引用。 因為如果要為鏈表添加結點,那么就會修改鏈表結構,所以必須傳遞引用才能夠保存修改后的結構。 */ void AddToTail(ListNode** pHead,int value) { ListNode* pNew=new ListNode();//新插入的結點 pNew->m_nValue=value; pNew->m_pNext=NULL; if(*pHead==NULL)//空鏈表 { *pHead=pNew; } else { ListNode* pNode=*pHead; while(pNode->m_pNext!=NULL) pNode=pNode->m_pNext; pNode->m_pNext=pNew; } } //求鏈表中間結點 ListNode* MidNodeInList(ListNode* pHead) { if(pHead==NULL) return NULL; ListNode* pNode=pHead;//當前結點 ListNode* pMidNode=pHead;//中間結點 //這里的判斷條件是下一個結點不為空且下下個結點也不為空 while(pNode->m_pNext!=NULL&&pNode->m_pNext->m_pNext!=NULL) { pNode=pNode->m_pNext->m_pNext; pMidNode=pMidNode->m_pNext; } return pMidNode; } void main() { //創建結點 ListNode* pNode1=CreateListNode(1);//創建一個結點 PrintList(pNode1);//打印 //往鏈表中添加新結點 AddToTail(&pNode1,2);//為鏈表添加一個結點 AddToTail(&pNode1,3);//為鏈表添加一個結點 AddToTail(&pNode1,4);//為鏈表添加一個結點 AddToTail(&pNode1,5);//為鏈表添加一個結點 AddToTail(&pNode1,6);//為鏈表添加一個結點 AddToTail(&pNode1,7);//為鏈表添加一個結點 //打印鏈表 PrintList(pNode1);//打印 //求鏈表的中間結點 ListNode* MidNode=MidNodeInList(pNode1); PrintListNode(MidNode); system("pause"); }
2.判斷一個單向鏈表是否形成了環狀結構。和前面的問題一樣,定義兩個指針,同時從鏈表的頭結點出發,一個指針一次走一步,另外一個指針一次走兩步。如果走得快的指針追上了走得慢的指針,那么鏈表就是環狀結構;如果走得快的指針走到了鏈表的末尾(m_pNext指向NULL)都沒有追上走得慢的指針,那么鏈表就不是環狀結構。
代碼實例如下:
View Code
#include<iostream> #include<stdlib.h> #include<stack> using namespace std; //鏈表結構 struct ListNode { int m_nValue; ListNode* m_pNext; }; //創建一個鏈表結點 ListNode* CreateListNode(int value) { ListNode *pNode=new ListNode(); pNode->m_nValue=value; pNode->m_pNext=NULL; return pNode; } //連接兩個結點 void ConnectListNode(ListNode* pCurrent,ListNode* pNext) { if(pCurrent==NULL) { cout<<"前一個結點不能為空"<<endl; exit(1); } else { pCurrent->m_pNext=pNext; } } //遍歷鏈表中的所有結點 void PrintList(ListNode* pHead) { ListNode *pNode=pHead; while(pNode!=NULL) { cout<<pNode->m_nValue<<" "; pNode=pNode->m_pNext; } cout<<endl; } //輸出鏈表中的某一結點的值 void PrintListNode(ListNode* pNode) { if(pNode == NULL) { printf("The node is NULL\n"); } else { printf("The key in node is %d.\n", pNode->m_nValue); } } //判斷鏈表中是否有環 int IsLootList(ListNode* pHead) { if(pHead==NULL) return NULL; ListNode* pFirst=pHead;//第一個指針,步長為2 ListNode* pSecond=pHead;//第二個指針,步長為1 while(pFirst->m_pNext!=NULL&&pFirst->m_pNext->m_pNext!=NULL) { pFirst=pFirst->m_pNext->m_pNext; pSecond=pSecond->m_pNext; if(pFirst==pSecond) return 1; } return 0; } void main() { //創建結點 ListNode* pNode1=CreateListNode(1);//創建一個結點 ListNode* pNode2=CreateListNode(2);//創建一個結點 ListNode* pNode3=CreateListNode(3);//創建一個結點 ListNode* pNode4=CreateListNode(4);//創建一個結點 ListNode* pNode5=CreateListNode(5);//創建一個結點 ListNode* pNode6=CreateListNode(6);//創建一個結點 ListNode* pNode7=CreateListNode(7);//創建一個結點 //連接結點 ConnectListNode(pNode1,pNode2);//連接兩個結點 ConnectListNode(pNode2,pNode3);//連接兩個結點 ConnectListNode(pNode3,pNode4);//連接兩個結點 ConnectListNode(pNode4,pNode5);//連接兩個結點 ConnectListNode(pNode5,pNode6);//連接兩個結點 ConnectListNode(pNode6,pNode7);//連接兩個結點 ConnectListNode(pNode7,pNode1);//連接兩個結點 //打印鏈表 //PrintList(pNode1);//打印 if(IsLootList(pNode1)) { cout<<"存在環"<<endl; } else { cout<<"不存在環"<<endl; } system("pause"); }
