【算法題解】線性表相關

本文在不同平臺收集了線性表相關的算法測試題,會持續更新。
當前內容如下

從尾到頭打印鏈表

來源:劍指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(2
N)

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;
        }
    }

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