本文在不同平臺收集了線性表相關的算法測試題,會持續更新。
當前內容如下
從尾到頭打印鏈表
來源:劍指offer 牛客網
有三種思路:
第一就是利用棧先入後出的特性完成;
第二就是將鏈表元素順序存下來,然後進行數組翻轉;
第三是利用遞歸。
1.棧思路:
class Solution {
public:
vector<int> printListFromTailToHead(ListNode* head) {
vector<int> value;
ListNode *p=NULL;
p=head;
stack<int> stk;
while(p!=NULL){ //將鏈表元素順序入棧
stk.push(p->val);
p=p->next;
}
while(!stk.empty()){ //出棧,元素保存至vector
value.push_back(stk.top());
stk.pop();
}
return value;
}
};
2.數組翻轉:
class Solution {
public:
vector<int> printListFromTailToHead(ListNode* head) {
vector<int> value;
ListNode *p=NULL;
p=head;
while(p!=NULL){
value.push_back(p->val);
p=p->next;
}
//reverse(value.begin(),value.end()); //可以用C++自帶的翻轉函數
//也可以自己實現翻轉
int temp=0;
int i=0, j=value.size()-1;
while(i<j){
temp=value[i]; //也可以用swap函數,swap(value[i],value[j]);
value[i]=value[j];
value[j]=temp;
i++; j--;
}
return value;
}
};
遞歸思路:
class Solution {
public:
vector<int> value;
vector<int> printListFromTailToHead(ListNode* head) {
ListNode *p=NULL;
p=head;
if(p!=NULL){
if(p->next!=NULL){
printListFromTailToHead(p->next);
}
value.push_back(p->val);
}
return value;
}
};
排序鏈表的合併
題目描述:
輸入兩個單調遞增的鏈表,輸出兩個鏈表合成後的鏈表,當然我們需要合成後的鏈表滿足單調不減規則。
來源:劍指offer
方法1:常規思路,建立一個新鏈表,通過依次比較兩個輸入鏈表的節點大小,連接到新鏈表。
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
{
if(!pHead1)
return pHead2;
if(!pHead2)
return pHead1;
ListNode* pHead; //頭指針
if( (pHead1->val) <(pHead2->val) ){
pHead = pHead1;
pHead1 = pHead1->next ;
}else{
pHead = pHead2;
pHead2 = pHead2->next ;
}
ListNode* cur_p = pHead ; //工作指針
while( pHead1 && pHead2 ){ //二者均不爲空
if( (pHead1->val) <(pHead2->val) ){
cur_p->next = pHead1;
pHead1 = pHead1->next ;
cur_p = cur_p->next;
}else{
cur_p->next = pHead2;
pHead2 = pHead2->next ;
cur_p = cur_p->next;
}
}
if(pHead1 == NULL) //若鏈表1遍歷完了
cur_p->next = pHead2;
if(pHead2 == NULL) //若鏈表2遍歷完了
cur_p->next = pHead1;
return pHead;
}
};
方法2:遞歸方法
//來源:https://www.nowcoder.com/questionTerminal/d8b6b4358f774294a89de2a6ac4d9337?f=discussion
class Solution {
public:
ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
{
ListNode* node=NULL;
if(pHead1==NULL){return pHead2;}
if(pHead2==NULL){return pHead1;}
if( pHead1->val > pHead2->val ){
node=pHead2;
node->next=Merge(pHead1,pHead2->next);
}else
{
node=pHead1;
node->next=Merge(pHead1->next,pHead2);
}
return node;
}
};
反轉鏈表
來源:劍指offer
題目描述:輸入一個鏈表,反轉鏈表後,輸出新鏈表的表頭。
解法一:通過記錄當前節點、當前節點的下一節點、當前節點的上一節點來遍歷實現反轉。
class Solution {
public:
ListNode* ReverseList(ListNode* pHead) {
if(pHead==NULL) return NULL;
ListNode* pNode=pHead;//當前指針
ListNode* pPrev=NULL;//當前指針的前一個結點
while(pNode!=NULL){//當前結點不爲空時才執行
ListNode* pNext=pNode->next;//鏈斷開之前一定要保存斷開位置後邊的結點
pNode->next=pPrev;//指針反轉
pPrev=pNode;
pNode=pNext;
}
//當pNode爲空時,跳出上面的循環,此時pPrev就是新的頭節點
return pPrev;
}
};
解法二:遞歸法
class Solution {
public:
ListNode* ReverseList(ListNode* pHead) {
//如果鏈表爲空或者鏈表中只有一個元素
if(pHead==NULL||pHead->next==NULL) return pHead;
//先反轉後面的鏈表,走到鏈表的末端結點
ListNode* pReverseNode=ReverseList(pHead->next);
//反轉指針,即將pHead下一節點的next點設置爲pHead【p1->p2 ==> p1<-p2】
pHead->next->next=pHead;
pHead->next=NULL;
return pReverseNode;
}
};
輸出單鏈表倒數第 K 個節點
題目:輸入一個單鏈表,輸出此鏈表中的倒數第 K 個節點。(去除頭結點,節點計數從 1 開始)。
轉自:鏈表算法面試問題,看我就夠了
方法1:兩次遍歷法.O(2N)
(1)遍歷單鏈表,遍歷同時得出鏈表長度 N 。
(2)再次從頭遍歷,訪問至第 N - K 個節點爲所求節點。
方法2:遞歸法.O(2N)
int num;//定義num值
ListNode* findKthTail(ListNode* pHead, int k) {
num = k;
if(pHead == NULL)
return NULL;
//遞歸調用
ListNode* pCur = findKthTail(pHead->next, k);
if(pCur != NULL)
return pCur;
else{
num--;// 遞歸返回一次,num值減1
if(num == 0)
return pHead;//返回倒數第K個節點
return NULL;
}
}
方法3:雙指針法.O(N)
(1)定義兩個指針 p1 和 p2 分別指向鏈表頭節點。
(2)p1 前進 K 個節點,則 p1 與 p2 相距 K 個節點。
(3)p1,p2 同時前進,每次前進 1 個節點。
(4)當 p1 指向到達鏈表末尾,由於 p1 與 p2 相距 K 個節點,則 p2 指向目標節點。
ListNode* findKthTail(ListNode *pHead, int K){
if (NULL == pHead || K == 0)
return NULL;
//p1,p2均指向頭節點
ListNode *p1 = pHead;
ListNode *p2 = pHead;
//p1先出發,前進K個節點
for (int i = 0; i < K; i++) {
if (p1)//防止k大於鏈表節點的個數
p1 = p1->_next;
else
return NULL;
}
while (p1)//如果p1沒有到達鏈表結尾,則p1,p2繼續遍歷
{
p1 = p1->_next;
p2 = p2->_next;
}
return p2;//當p1到達末尾時,p2正好指向倒數第K個節點
}
兩個鏈表的第一個公共節點
假定 List1長度: a+n ;List2 長度:b+n, 且 a<b
那麼 p1 會先到鏈表尾部, 這時p2 走到 a+n位置,將p1換成List2頭部
接着p2 再走b-a 步到鏈表尾部,這時p1也走到List2的b-a位置,還差a步就到可能的第一個公共節點。
將p2 換成 List1頭部,p2走a步也到可能的第一個公共節點。如果恰好p1==p2,那麼p1就是第一個公共節點。 或者p1和p2一起走n步到達列表尾部,二者沒有公共節點,退出循環。 同理a>=b.
時間複雜度O(n+a+b)
class Solution {
public:
ListNode* FindFirstCommonNode(ListNode* pHead1, ListNode* pHead2) {
ListNode* p1 = pHead1;
ListNode* p2 = pHead2;
while(p1 != p2) {
if(p1 != NULL) p1 = p1->next;
if(p2 != NULL) p2 = p2->next;
if(p1 != p2) {
if(p1 == NULL) p1 = pHead2;
if(p2 == NULL) p2 = pHead1;
}
}
return p1;
}
};
刪除排序鏈表中重複的節點
題目描述
在一個排序的鏈表中,存在重複的結點,請刪除該鏈表中重複的結點,重複的結點不保留,返回鏈表頭指針。 例如,鏈表1->2->3->3->4->4->5 處理後爲 1->2->5
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};
*/
//思考。。
class Solution {
public:
ListNode* deleteDuplication(ListNode* pHead)
{
if (pHead==NULL || pHead->next==NULL){return pHead;}
//首先添加一個頭節點,以方便碰到第一個,第二個節點就相同的情況
ListNode* Head = new ListNode(0);
Head->next = pHead;
// pre指針指向當前確定不重複的那個節點,而last指針相當於工作指針,一直往後面搜索。
ListNode* pre = Head;
ListNode* last = Head->next;
while(last != NULL ){
if(last->next != NULL && last->val != last->next->val){
pre = last; //不重複的節點直接移動pre指針即可
}
else if(last->next != NULL && last->val == last->next->val){
while(last->next != NULL && last->val == last->next->val){
last = last->next; //重複的節點讓它一直移動
}
pre->next = last->next;
}
last = last->next;
}
return Head->next;
}
};
鏈表中環的入口節點
題目描述:
給一個鏈表,若其中包含環,請找出該鏈表的環的入口結點,否則,輸出null。
思路:
參考鏈接:牛客網
首先給出兩個結論:
1、設置快慢指針,假如有環,他們最後一定相遇。
2、兩個指針分別從鏈表頭和相遇點繼續出發,每次走一步,最後一定相遇與環入口。
證明結論1:設置快慢指針fast和low,fast每次走兩步,low每次走一步。假如有環,兩者一定會相遇(因爲low一旦進環,可看作fast在後面追趕low的過程,每次兩者都接近一步,最後一定能追上)。
證明結論2:
設:
鏈表頭到環入口長度爲–a
環入口到相遇點長度爲–b
相遇點到環入口長度爲–c
則:相遇時
快指針路程=a+(b+c)k+b ,k>=1 其中b+c爲環的長度,k爲繞環的圈數(k>=1,即最少一圈,不能是0圈,不然和慢指針走的一樣長,矛盾)。
慢指針路程=a+b
快指針走的路程是慢指針的兩倍,所以:
(a+b)*2=a+(b+c)k+b
化簡可得:
a=(k-1)(b+c)+c 這個式子的意思是: 鏈表頭到環入口的距離=相遇點到環入口的距離+(k-1)圈環長度。其中k>=1,所以k-1>=0圈。所以兩個指針分別從鏈表頭和相遇點出發,最後一定相遇於環入口。
class Solution {
public:
ListNode* EntryNodeOfLoop(ListNode* pHead)
{
ListNode *fast=pHead, *low=pHead;
while(fast&&fast->next){
fast=fast->next->next;
low=low->next;
if(fast==low)
break;
}
if(!fast||!fast->next)return NULL;
low=pHead;//low從鏈表頭出發
while(fast!=low){//fast從相遇點出發
fast=fast->next;
low=low->next;
}
return low;
}
};
刪除鏈表中節點,要求時間複雜度爲O(1)
問題描述:
給定一個單鏈表中的表頭和一個等待被刪除的節點。請在 O(1) 時間複雜度刪除該鏈表節點。並在刪除該節點後,返回表頭。
轉自:鏈表算法面試問題,看我就夠了
示例:
給定 1->2->3->4,和節點 3,返回 1->2->4。
解題思想:
最普通的方法就是遍歷鏈表,複雜度爲O(n)。
如果我們把刪除節點的下一個結點的值賦值給要刪除的結點,然後刪除這個結點,這相當於刪除了需要刪除的那個結點。因爲我們很容易獲取到刪除節點的下一個節點,所以複雜度只需要O(1)。
示例:
單鏈表:1->2->3->4->NULL
若要刪除節點 3 。第一步將節點3的下一個節點的值4賦值給當前節點。變成 1->2->4->4->NULL,然後將就 4 這個結點刪除,就達到目的了。 1->2->4->NULL
如果刪除的節點的是頭節點,把頭結點指向 NULL。
如果刪除的節點的是尾節點,那隻能從頭遍歷到頭節點的上一個結點。
圖解過程:
代碼實現:
void deleteNode(ListNode *pHead, ListNode* pDelNode) {
if(pDelNode == NULL)
return;
if(pDelNode->next != NULL){
ListNode *pNext = pDelNode->next;
//下一個節點值賦給待刪除節點
pDelNode->val = pNext->val;
//待刪除節點指針指後面第二個節點
pDelNode->next = pNext->next;
//刪除待刪除節點的下一個節點
delete pNext;
pNext = NULL;
}else if(*pHead == pDelNode)//刪除的節點是頭節點
{
delete pDelNode;
pDelNode= NULL;
*pHead = NULL;
} else//刪除的是尾節點
{
ListNode *pNode = *pHead;
while(pNode->next != pDelNode) {
pNode = pNode->next;
}
pNode->next = NULL;
delete pDelNode;
pDelNode= NULL;
}
}