LeetCode 链表总结

LeetCode链表总结

以下所有的试题存出自LeetCode,每个题的解法思路均有参考和对比LeetCode上官方及各位大神们的题解(非常感谢平台和大家无私的分享),有些题的解法较为繁多,此处并未全部写出思路或者解题代码。

目录

1哑结点(假结点)

2两种删除方法

3两种插入方法:

4双指针:

4.1删除结点

4.1.1移除链表元素:

4.1.2删除排序链表中的重复元素

4.1.3删除排序链表中的重复元素 II

4.1.4非排序链表删除重复元素 

4.2反转链表

5快慢指针(精准定位):

5.1关于环

5.2两个链表相交结点

5.3倒数结点

5.4中间结点

6先入后出:

7递归(重复操作)

8综合:

8.1反转链表

8.2开辟新链表

8.3灵光乍现

双向链表


我们默认定义链表结构如下(LeetCode链表的统一定义类型): 

 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };

1哑结点(假结点)

设置一个结点指向原始链表的头结点,方便更改链表结构,也方便返回新的头结点

ListNode dummy(-1);
dummy.next = head;
ListNode* Pre = &dummy;
ListNode* PCur = head;


..........



return dummy.next;

2两种删除方法

删除链表中的某个结点,有两种方法,第一种方法,得到要删除结点的前一个结点指针,将其指向要删除结点的后一个结点,完成。

这是最常规也是最常用的方法,但是在有些情况下,我们无法得到要删除结点的前一个结点指针,那么就指针使用第二种方法。

 

覆盖内容,将要删除结点的后一个结点的值,赋给要删除的结点,然后将要删除的结点的next指针指向下下一个结点(也就是跳过了

要删除结点的后一个结点)。覆盖内容有一个缺点,就是无法删除尾结点,一旦涉及尾结点的删除,只能使用常规结点进行操作。

LeetCode中关于链表的基本概念和常见题型介绍:https://leetcode-cn.com/explore/learn/card/linked-list/193/singly-linked-list/

下面部分附图也是出自LeetCode

第一种方法:常规操作

                 

第二种方法:覆盖操作

           

代码如下(以面试题 02.03. 删除中间节点为例):

class Solution {
public:
    void deleteNode(ListNode* node) {

        ListNode* Nnext = node->next;
        node->val = Nnext->val;
        node->next = Nnext->next;       
    }
};

 

关于利用第二种方法进行结点删除的题目如下:

相关题目:

面试题 02.03. 删除中间节点https://leetcode-cn.com/problems/delete-middle-node-lcci/ 

删除链表中的节点https://leetcode-cn.com/problems/delete-node-in-a-linked-list/

面试题18. 删除链表的节点https://leetcode-cn.com/problems/shan-chu-lian-biao-de-jie-dian-lcof/

而关于面试题18,题目要求删除结点,没有特指中间结点,所以该结点包换收尾结点,使用双指针哑结点会很轻易的完成题解

当然,如果面试要求不能使用哑结点,也要知道应该如何入手。 

3两种插入方法:

尾插法:最常见的元素插入方法,如图所示

 

头插法:在特定场合,比如反转输出链表,使用栈,或者头插法都是很好的选择,如图所示

 

4双指针:

单向链表中大部分操作,核心都是在遍历中完成各项操作,删除,插入,替换,反转,都是在遍历中完成的。

那么对于单向链表来说,因为单向,所以前一个结点是最为宝贵的,很多操作都牵扯到前结点,由此,也就有了双指针的解题方法。

设置两个步长差1的指针,Pre和PCur,Pre指向nullptr,dummy或者头结点head,pCur指向head或者head->next;

然后开始遍历操作,在遍历过程中,完成各项操作,下面看看设计双指针的例题都有哪儿些。

4.1删除结点

4.1.1移除链表元素

https://leetcode-cn.com/problems/remove-linked-list-elements/

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        //双指针
        if(head == nullptr)
        return head;
        ListNode dummy(-1);
        dummy.next = head;
        ListNode* Pre = &dummy;
        ListNode* PCur = head;
        while(PCur)
        {
            if(PCur->val == val)
            {
                Pre->next = PCur->next;
                PCur->next = nullptr;
                PCur = Pre->next;
            }
            else
            {
                Pre = PCur;
                PCur = PCur->next;
            }

        }
        return dummy.next;

        //递归
        if(head == nullptr)
        return nullptr;
        if(head->val == val)
        {
            head = removeElements(head->next,val);
        }
        else
        {
            head->next = removeElements(head->next,val);
        }
        return head;
    }
};

非常典型的双指针的应用操作,同样的使用递归也是非常好的办法。 

4.1.2删除排序链表中的重复元素

https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list/

 

方法一:使用Hash表,在表中寻找元素,没有找到,放入表中,找到了,删除结点 

方法二:注意,这是一个排序链表,数字都是按照顺序出现的,那么相邻两个元素不断比较,就知道重复不重复了,重复的删除即可。

class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        //Hash表
        if(head == nullptr)
        return head;
        unordered_map<int,int>M;
        ListNode* Pre = nullptr;
        ListNode* PCur = head;
        while(PCur)
        {
            if(M.find(PCur->val) != M.end())//找到了
            {
                Pre->next = PCur->next;
                PCur = Pre->next;
            }
            else
            {
                M[PCur->val] = PCur->val;
                Pre = PCur;
                PCur = PCur->next;
            }
        }
        return head;
        //双指针
        if(head == nullptr)
        return head;
        ListNode* Pre = head;
        ListNode* PCur = head->next;
        while(PCur)
        {
            if(Pre->val == PCur->val)
            {
                Pre->next = PCur->next;
                PCur->next = nullptr;
                PCur = Pre->next;
            }
            else
            {
                Pre = PCur;
                PCur = PCur->next;
            }
        }
        return head;   
    }
};

4.1.3删除排序链表中的重复元素 II

https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list-ii/

 那么关于删除元素的进阶版,又该如何呢?

Hash表显然不好用了,还是要抓住链表是排序链表的特点,利用双指针。但是要注意细节,合理利用dummy结点,前指针保存前置结点,后指针不断loop,直到删除完全部的重复元素,然后再继续,一定要注意,重复的个数不一定是2。

class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        //易读部分
        if(head == nullptr)
        return head;
        ListNode dummy(-1);
        dummy.next = head;
        ListNode* Pre = &dummy;
        ListNode* PCur = head;
        while(PCur)
        {
            if(PCur->next&&PCur->val == PCur->next->val)
            {
                int val = PCur->val;
                Pre->next = PCur->next->next;
                PCur = PCur->next->next;
                while(PCur&&PCur->val == val)
                {
                    Pre->next = PCur->next;
                    PCur = PCur->next;
                }
            }
            else 
            {
                Pre = PCur;
                PCur = PCur->next;
            }
        }
        return dummy.next; 
        //高质量
        if(head == nullptr)
        return nullptr;
        ListNode dummy(-1);
        dummy.next = head;
        ListNode* Pre,* Pcur = &dummy;//设置一个临时的头指针
        while(Pcur)
        {
            Pre = Pcur;
            Pcur = Pcur->next;//放在前面就不用过多的判断了
            while(Pcur&&Pcur->next&&Pcur->val == Pcur->next->val)
            {
                int temp = Pcur->val;
                Pcur = Pcur->next;
                while(Pcur&&Pcur->val == temp)
                {
                    Pcur = Pcur->next;
                }
            }
            Pre->next = Pcur;
        }
        return dummy.next;
 
    }
};

4.1.4非排序链表删除重复元素 

面试题 02.01. 移除重复节点 https://leetcode-cn.com/problems/remove-duplicate-node-lcci/

那么如果失去了排序这个最大的特征,同时要求不使用额外的空间(比如Hash表,Hash表做这个题依旧秒杀),该如何应对 ?

那么只能使用比较暴力的解法,一个指针track指向第一个结点,设置两个游标指针,遍历剩下的结点找相同的点,完成遍历后,track再后移。时间复杂度O(N^2),牺牲时间去换取空间。

class Solution {
public:
    ListNode* removeDuplicateNodes(ListNode* head) {
        if(head == nullptr)
        return nullptr;
        ListNode * Pre = nullptr;
        ListNode * PCur = nullptr;
        ListNode * Track = head;

        while(Track!=nullptr)
        {
            Pre = Track;
            PCur = Track->next;
            while(PCur != nullptr)
            {
                if(PCur->val == Track->val)//相等,进行删除操作
                {
                    Pre->next = PCur->next;
                    PCur = PCur->next;
                }
                else
                {
                    Pre = PCur;
                    PCur = PCur->next;
                }
            }
            Track = Track->next;
        }
        return head;
    }
};

4.2反转链表

反转链表 https://leetcode-cn.com/problems/reverse-linked-list/

反转链表 II https://leetcode-cn.com/problems/reverse-linked-list-ii/

反转一个单链表,递归是一个非常合适的方法,如果不使用递归,有什么好的方法?

双指针遍历,让后指针的next指向前指针,最终返回尾结点也就是新链表的头结点,注意细节,头结点的next记着指向nullptr,否则会循环输出。

那么如果是领一个链表的n到m个结点反转呢?

同理,注意保留关键结点即可。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* reverseBetween(ListNode* head, int m, int n) {
        if(head == nullptr) return head;
        ListNode * NewHead = head;
        int delta = n - m + 1;
        if(m == 1) return ReverseList(NewHead,delta);//从第一个位置就开始反转
        while(m > 2)//找到反转开始的前一个结点
        {
            m--;
            if(NewHead) NewHead = NewHead->next;
        }
        NewHead->next = ReverseList(NewHead->next,delta);
        return head;
    }
    ListNode * ReverseList(ListNode* head, int delta)//反转链表部分,返回反转后的新头部
    {
        if(head == nullptr) return head;
        cout<<delta<<endl;
        ListNode* Pre = nullptr;
        ListNode* temp = head;
        ListNode * Next;
        while(temp&&delta)
        {
            delta--;
            Next = temp->next;
            temp->next = Pre;
            Pre = temp;
            temp = Next;
        }
        head->next = Next;
        return Pre;
    }
};

 

5快慢指针(精准定位):

 一旦牵扯到精准定位,比如找环的起点,找倒数某个结点,两个链表的公共结点,一个链表的中间结点等

即精准的找到链表中的某个结点的题,基本都是快慢指针

5.1关于环

                    

关于链表中的环,示意图如上图所示,单向链表中有一个环,常见的考查内容:

  1. 判断是否有环
  2. 计算环的大小
  3. 确定环开始的位置 

环形链表 https://leetcode-cn.com/problems/linked-list-cycle/ 

环形链表 II  https://leetcode-cn.com/problems/linked-list-cycle-ii/

都是精准定位的问题,毫无疑问,快慢指针,fast和slow指针都是从head出发,一个一次走一步,一个一次走两步,如果有环,fast指针必定会追上slow,必定会在环中相遇,假设相遇点为C点,没有追上,那就是单向链表中没有环。(解决问题1)

既然在环中的某个结点相遇,也就是找到了环中的某个结点,那么循环一周再次回到这个结点时,也就就计算出了环的大小。

(解决问题2)

因为fast的步长是slow的二倍,又在C点相遇,假设示意图中A到B结点距离为X,B到C结点距离为Y,C到B结点的距离为Z。

X+Y+Z+Y= 2*(X+Y);等号左边是fast指针走的路程,等号右边是slow指针走的路程。

显然得出,Z = X;

那么从相遇点到环的起点,和从head到环的起点,路程相等,那么不断迭代指针进行前进操作,相遇点,就是环的起点(解决问题3)

 

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        if(head == nullptr||head->next == nullptr)
        return nullptr; 

        ListNode * slow = head;
        ListNode * fast = head;

        while(fast&&fast->next)
        {
            slow = slow->next;
            fast = fast->next->next;
            if(slow == fast)
            break;
        }
        

        ListNode * PCur = head;
        while(PCur&&fast&&fast->next)
        {
            if(PCur == fast)
            return fast;
            PCur = PCur->next;
            fast = fast->next;
        }
        return nullptr; 

    }
};

 除了以上三种典型的环外,还有一些关于环的编写的题目:

旋转链表 https://leetcode-cn.com/problems/rotate-list/

此题,需要把单链表一开始就处理成环(及循环链表)。然后根据题目的意思,去循环找新的头结点,找到头结点了,那么尾结点就在头结点的前面,之后进行断开操作即可。

class Solution {
public:
    ListNode* rotateRight(ListNode* head, int k) {
        if(head == nullptr)
        return nullptr;
        unordered_map<int,int>M;
        int Size = 0;
        ListNode* PCur = head;
        while(PCur->next)
        {
            PCur = PCur->next;
            Size++;
        }
        PCur->next = head;//循环链表
        Size++;

        PCur = head;
        int Index = k%Size;
        for(int i = 0;i<Size - Index -1;++i)
        {
            PCur = PCur->next;
        }
        ListNode* Begin = PCur->next;
        PCur->next = nullptr;

        return Begin;
    }
};

 

5.2两个链表相交结点

面试题52. 两个链表的第一个公共节点 https://leetcode-cn.com/problems/liang-ge-lian-biao-de-di-yi-ge-gong-gong-jie-dian-lcof/

面试题 02.07. 链表相交 https://leetcode-cn.com/problems/intersection-of-two-linked-lists-lcci/

相交链表 https://leetcode-cn.com/problems/intersection-of-two-linked-lists/

此类题目基本都是如下描述:

使用Hash表,A链表全部放入Hash表,key值为地址,之后在B链表中遍历,每到一个指针就在Hash表中寻找,找到,自然有相遇的结点,找不到,自然是不相遇。

除此之外,不使用额外空间,就使用快慢指针,因为两个链表的长度不等,也有可能相等,如果相等,两个指针均从AB链表的头出发,那么相遇处一定是链表的相交点,但是如果链表长度不相等呢?我们要如何使用指针来消除这个距离上的差值?

方法如下:两个指针分别从A,B链表的头部出发,步长均为1,一旦达到尾结点,将指向另一条链表的头部,比如一开始指向A结点head的指针APointer,随着不断遍历,达到nullptr时,理科让APointer指针指向B链表的头部,继续遍历。

当APointer和BPointer相遇时,就是两个链表的相交点。

假设A链表长度为X,B链表长度是Y。

X-Y = Deta;

又因为相交之后的链表长度相对,那么Deta只会体现在相交之前的这部分链表上,最好的办法就是上述办法,完成一次遍历后直接将指针换到另一个链表的头指针上,直接消除了Deta差值,变成了相同长度链表的情况。

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        if(headA == nullptr && headB == nullptr)
        return nullptr;
        ListNode * PCurA = headA;
        ListNode * PCurB = headB;

        while(PCurA != PCurB)
        {
            PCurA = (PCurA == nullptr ? headB:PCurA->next);
            PCurB = (PCurB == nullptr ? headA:PCurB->next);
        }
        return PCurA;
    }
};

5.3倒数结点

面试题 02.02. 返回倒数第 k 个节点 https://leetcode-cn.com/problems/kth-node-from-end-of-list-lcci/

删除链表的倒数第N个节点 https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/

面试题22. 链表中倒数第k个节点 https://leetcode-cn.com/problems/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof/

 

找正数第N个结点并且删除,那实在是太容易了,那么倒数呢?看图说明问题:

              

 slow指针指向Dummy,fast指针和slow相差n个步长,倒数第n个元素也是要操作的元素,之后遍历,直到fast等于nullptr的时候,slow指向了倒数第n个元素。

当然了,有时候是要找到倒数第n个元素,有时候是要删除,那么slow和fast的开始指向,相差步数,都是需要调整的,但是原理都是一样的。都是利用快慢指针想办法制造“倒数”,及从fast往slow数。

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        if(head == nullptr || n <= 0)
        return nullptr;

        ListNode dummy(-1);
        dummy.next = head;

        ListNode* Ppre = &dummy;
        ListNode* Pend = head;
        int k = n;

        while(Pend!=nullptr)
        {
            while(k!=0&&Pend != nullptr)
            {
                k--;
                Pend = Pend->next;
            }
            if(Pend == nullptr)
            break;
            Pend = Pend->next;
            Ppre = Ppre->next;
        }
        if(Ppre->next!=nullptr)
        Ppre->next = Ppre->next->next;
        return dummy.next;
        
    }
};

5.4中间结点

链表的中间结点 https://leetcode-cn.com/problems/middle-of-the-linked-list/

链表的中间结点  https://leetcode-cn.com/problems/middle-of-the-linked-list/

寻找链表的中间元素,【1,2,3,4,5】中间值。【1,2,3,4】中间值:3。

slow一次步长为1,fast步长为2。当fast->next或者fast等于nullptr的时候,slow也就是中间值了,这也是利用快慢指针模拟“一半”的状态。

class Solution {
public:
    ListNode* middleNode(ListNode* head) {
        //典型的快慢指针
        if(head == nullptr)
        return nullptr;

        ListNode* slow = head;
        ListNode* fast = head;

        while(fast&&slow)
        {
            if(fast->next==nullptr)
            break;
            fast = fast->next->next;
            slow = slow->next;
        }

        return slow;

    }
};

 

6先入后出:

两数相加 IIhttps://leetcode-cn.com/problems/add-two-numbers-ii/

栈:先进后出,这个性质用好了可不得了,会简化非常多的指针操作步骤,链表中也有部分题目也会借助于栈的结构特点

 链表直接入栈,高位栈底低位栈顶,弹出直接加,注意进位即可:(本体务必注意的是进位问题和两个链表长度题目中可没有说相等,不等长的情况务必考虑)

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        //stack<int>
        if(l1 == nullptr&&l2 == nullptr) return nullptr;
        if(l1 != nullptr&&l2 == nullptr) return l1;
        if(l1 == nullptr&&l2 != nullptr) return l2;

        stack<int> Dl1,Dl2,Res;
        ListNode* Temp = l1;
        while(Temp) {Dl1.push(Temp->val);Temp = Temp->next;}//分别入栈
        Temp = l2;
        while(Temp) {Dl2.push(Temp->val);Temp = Temp->next;}//分别入栈
        int YN = 0;
        while(Dl1.size()||Dl2.size())
        {
            int tempNum = YN;
            if(Dl1.size()) {tempNum += Dl1.top();Dl1.pop();}
            if(Dl2.size()) {tempNum += Dl2.top();Dl2.pop();}
            YN = tempNum/10;//是否进位,进位多少
            tempNum = tempNum%10;
            Res.push(tempNum);
        }
        if(YN != 0) Res.push(YN);
        //创建新的链表
        ListNode* head = new ListNode(-1);
        ListNode* pre = nullptr;
        // cout<<Res.size()<<endl;
        while(Res.size())
        {
            ListNode* Item = new ListNode();
            if(head->val == -1) head = Item;
            if(pre == nullptr) pre = Item;
            else{pre->next = Item;pre = Item;}
            Item->val = Res.top();
            cout<<Item->val<<endl;
            Res.pop();
        }
        return head;

    }
};

其他求和相关的题目:面试题 02.05. 链表求和https://leetcode-cn.com/problems/sum-lists-lcci/

 两数相加:https://leetcode-cn.com/problems/add-two-numbers/

当然,还有处理回文链表,完全可以使用栈,先将元素全部压入栈中,此时栈顶就是尾结点的val,栈底就是头结点的val,之后从链表的头结点开始遍历,对比栈顶元素和链表指针所指元素是否相等,如果是回文,那么链表正序遍历和逆序遍历元素都是一样的,此时的栈就是充当逆序遍历的一种方式。

回文链表 https://leetcode-cn.com/problems/palindrome-linked-list/

面试题 02.06. 回文链表https://leetcode-cn.com/problems/palindrome-linked-list-lcci/

 

 

 

class Solution {
public:
    bool isPalindrome(ListNode* head) {
        stack<int> s;
        ListNode *p = head;
        while(p)
        {
            s.push(p->val);
            p = p->next;
        }
        p = head;
        while(p)
        {
            if(p->val != s.top())
            {
                return false;
            }
            s.pop();
            p = p->next;
        }
        return true;

    }
};

但是如果题目有要求,比如不允许使用额外的空间,那么就没办法使用栈了,只能使用其他方法。

7递归(重复操作)

简单可执行的重复操作,在链表中递归是个非常常用的方法,很多问题都可以利用递归进行解决 。

合并两个有序链表  https://leetcode-cn.com/problems/merge-two-sorted-lists/

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
		if(l1 == nullptr)
        return l2;
        else if(l2 == nullptr)
        return l1;

        ListNode * NewHead = nullptr;
        if(l1->val < l2->val)
        {
            NewHead = l1;
            NewHead->next = mergeTwoLists(l1->next,l2);
        }
        else
        {
            NewHead = l2;
            NewHead->next = mergeTwoLists(l1,l2->next);
        }
        return NewHead;
    }
};

反转链表 https://leetcode-cn.com/problems/fan-zhuan-lian-biao-lcof/

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if(head == nullptr) return nullptr;
        if (head&&head->next == nullptr) //找到链表的最后一个值
        {
            return head;
        }
        ListNode* ret = reverseList(head->next);
        head->next->next = head;
        head->next = NULL;
        return ret;//返回反转链表的新头指针
    }
};

两两交换链表中的节点 https://leetcode-cn.com/problems/swap-nodes-in-pairs/

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        if(head == nullptr||head->next == nullptr)
        return head;

        ListNode* Pnext = head->next;
        ListNode* Insethead = Pnext->next;

        Pnext->next = head;
        head->next = swapPairs(Insethead);
        return Pnext;  
    }
};

删除排序链表中的重复元素 II  https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list-ii/

class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
	    if(head == nullptr)
        return nullptr;
        if(head->next != nullptr&&head->val == head->next->val)
        {
            while(head->next != nullptr&&head->val == head->next->val)
            head = head->next;
            // return deleteDuplicates(head);//删除重复元素,但是会剩下一个
            return deleteDuplicates(head->next);//重复元素全部删除
        }
        else 
        {
            head->next = deleteDuplicates(head->next);
            return head;
        }
    }
};

8综合:

8.1反转链表

回文链表 https://leetcode-cn.com/problems/palindrome-linked-list/

面试题 02.06. 回文链表https://leetcode-cn.com/problems/palindrome-linked-list-lcci/

回文:如果不使用栈,那就反转链表。找到中间结点,然后后半段反转,然后一个从头一个从尾,两个指针往中间结点遍历,对比数值

 

 

class Solution {
public:
    bool isPalindrome(ListNode* head) {
        if(head == nullptr||head->next == nullptr)//没有也算
        return true;//算回文

        //找到中间结点
        ListNode* slow = head;
        ListNode* fast = head;
        while(fast&&fast->next)
        {
            slow = slow->next;
            fast = fast->next->next;
        }
        //后面反转链表
        ListNode* pCur = slow;
        ListNode* pnext = slow->next;
        while(pnext)
        {
            ListNode* temp = pnext->next;
            pnext->next = pCur;
            pCur = pnext;
            pnext = temp;

        }
        slow->next = nullptr;
        //比较
        // ListNode* tempHead = head;
        while(head&&pCur)
        {
            if(head->val != pCur->val)
            return false;

            head = head->next;
            pCur = pCur->next;
        }
        return true;
    }
};

 

8.2开辟新链表

有时候需要对原版的链表进行分割,比如拆出奇偶项等情况。

有时候需要重新将两个链表合并。

这个需要重新建立一个新的头结点,连接被分割出的链表也好,连接重新排序的链表也好。本质都是一样的,都是对链表的理解和指针的操作提出了高要求。

奇偶链表;https://leetcode-cn.com/problems/odd-even-linked-list/ 

此时,就是要将原来的链表分割为两个链表,然后重排这两个链表。

与合并链表异曲同工,设置一个新指针,指向偶数部分的第一个结点,然后两个个指针,一个从第一个奇数结点出发,一个从偶数结点开始遍历, 断开旧的链接,建立新的链接,之后得到一组奇数链表,一组偶数链表,然后将链表组合即可。

class Solution {
public:
    ListNode* oddEvenList(ListNode* head) {
        if(head == nullptr||head->next == nullptr)
        return head;
        ListNode* NewHead = head->next;
        ListNode* OldPCur = head;
        ListNode* NewPCur = NewHead;
        while(NewPCur&&NewPCur->next)
        {
            OldPCur->next = NewPCur->next;
            OldPCur = OldPCur->next;
            NewPCur->next = OldPCur->next;
            NewPCur = NewPCur->next;
        }
        OldPCur->next = NewHead;

        return head;
    }
};

 合并两个有序链表  https://leetcode-cn.com/problems/merge-two-sorted-lists/

此题使用递归非常简单,但是不使用递归的话

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        // return l2;
        
        if(l1 == nullptr&&l2 != nullptr) return l2;
        if(l1 != nullptr&&l2 == nullptr) return l1;
        if(l1 == nullptr&&l2 == nullptr) return nullptr;
        
        ListNode* One = l1;
        ListNode* Two = l2;
        ListNode* head = nullptr;
        ListNode* pre = nullptr;
        while(One&&Two)
        {
            ListNode* temp = new ListNode;
            if(head == nullptr) head = temp;

            if(pre == nullptr) pre = temp;
            else {pre->next = temp;pre = temp;}
            // cout<<One->val<<"   "<<Two->val<<endl;
            if(One->val <= Two->val)
            {
                temp->val = One->val;
                One = One->next;
            }
            else
            {
                temp->val = Two->val;
                Two = Two->next;
            }
        }
        while(One) 
        {
            ListNode* temp = new ListNode;
            pre->next = temp;pre = temp;
            temp->val = One->val;
            One = One->next;
        }
        while(Two) 
        {
            ListNode* temp = new ListNode;
            pre->next = temp;pre = temp;
            temp->val = Two->val;
            Two = Two->next;
        }
        return head;
        
    }
};

但是本题可以再做扩展,我们不使用额外的空间,原地合并,不开辟新的空间

以下算法参考:https://leetcode-cn.com/problems/merge-two-sorted-lists/solution/he-bing-liang-ge-you-xu-lian-biao-by-leetcode-solu/

 

我们创建一个Prehead之中,并且让Pre指向Prehe,然后进入循环,比较l1的l2目前所指的内容大小,让pre的next指向小的,如图中,指向了0,然后我们保持l1指针不动,移动l2,再移动pre,此时的pre指向0,l2指向3,l1还指向1。

如果再次比较的话,那么pre指向的显然是1,l1后移。那么此时新的内容也串联了起来,逻辑清晰,代码简单,阅读方便

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        if(!l1&&!l2) return nullptr;
        ListNode* preHead = new ListNode;
        ListNode* prev = preHead;
        while(l1&&l2)
        {
            if(l1->val <l2->val)
            {
                prev->next = l1;
                l1 = l1->next;
            }
            else 
            {
                prev->next = l2;
                l2 = l2->next;
            }
            prev = prev->next;  
        }
        prev->next = l1 == nullptr ? l2 : l1;
        return preHead->next; 
    }
};

如果使用递归,我们该怎么处理?

我们看一下原链表被破坏后,新的链表是如何组成的

小的部分,作为新链表的起点,我们开程序:

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        if(!l1) return l2;
        else if(!l2) return l1;
        else if(l1->val<l2->val) {l1->next = mergeTwoLists(l1->next,l2);return l1;}
        else {l2->next = mergeTwoLists(l1,l2->next);return l2;}
    }
};

 

8.3灵光乍现

以下题解都比较综合,结题方法也都非常多变和灵活,不是很好归类,故放在此处。

 面试题35. 复杂链表的复制https://leetcode-cn.com/problems/fu-za-lian-biao-de-fu-zhi-lcof/

 将新结点放在原始结点的后面,第一次遍历赋值内容和插入新结点。

第二次遍历,辅助结点的复杂内容。这个部分一定要注意,在分离新旧链表之前,就要把random指针处理好,因为分离之后,就丢失了random指针的信息。

第三次遍历,分离新链表。

/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* next;
    Node* random;
    
    Node(int _val) {
        val = _val;
        next = NULL;
        random = NULL;
    }
};
*/

class Solution {
public:
    Node* copyRandomList(Node* head) {
        if(head == nullptr) return nullptr;
        //拷贝
        Node*Cur = head;
        while(Cur)
        {
            Node* Temp = new Node(Cur->val);
            Node* Next = Cur->next;
            Cur->next = Temp;
            Temp->random = Cur->random;
            Temp->next = Next;
            Cur = Next;
        }
        //目前已经完成复制,现在分割,一定要注意随意,是指向自己的随机,不是以前老版本的随机
        Node*NewHead = new Node(INT_MIN);
        Node*NewPre = nullptr;
        Cur = head;
        while(Cur)
        {
            Node* Another = Cur->next;
            if(Cur->random) Another->random = Cur->random->next;
            Cur = Another->next;
        }
        Cur = head;
        while(Cur)
        {
            Node* Another = Cur->next;
            if(NewHead->val == INT_MIN) {NewHead = Another;}
            if(NewPre == nullptr) NewPre = Another;
            else {NewPre->next = Another;NewPre = Another;}
            // cout<<Cur->random<<"  "<<Another->random<<endl;
            // if(Cur->random) Another->random = Cur->random->next;
            Cur->next = Another->next;//恢复原来的样子
            Cur = Another->next;
            Another->next = nullptr;
        }

        return NewHead;
        
    }
};

重排链表  https://leetcode-cn.com/problems/reorder-list/

不使用额外的空间,此题如何下手?找规律,不难发现,尾结点都是链表的中间结点,找中间结点岂不容易。

然后发现,中间结点后面的结点,都按照顺序插在前半段链表的结点之间。 

那么第一步:找中间结点

第二步:反转后半段链表

第三步:将后半段结点插入到前半段中

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    void reorderList(ListNode* head) {
        if(head == nullptr) return;
        stack<int> Back;
        ListNode* slow = head;
        ListNode* fast = head;
        while(fast&&fast->next)
        {
            slow = slow->next;
            fast = fast->next->next;
        }
        while(slow)
        {
            ListNode* Temp = slow->next;
            slow->next = nullptr;
            slow = Temp;
            if(slow) Back.push(slow->val);
        }
        slow = head;
        while(slow&&Back.size())
        {
            ListNode* Temp= new ListNode(Back.top());
            Back.pop();
            ListNode* Next =  slow->next;
            slow->next = Temp;
            Temp->next = Next;
            slow = Next;
        }
        return;
        
    }
};

1171. 从链表中删去总和值为零的连续节点

:https://leetcode-cn.com/problems/remove-zero-sum-consecutive-nodes-from-linked-list/

本题更多的是一道前缀和问题

class Solution {
public:
    ListNode* removeZeroSumSublists(ListNode* head) {
        if(head == nullptr)
        return nullptr;

        // 保存前缀和
        unordered_map<int, ListNode*> prefixSum;
        // 因为头结点也有可能会被消掉,所以这里加一个虚拟节点作为头结点
        ListNode* dummy = new ListNode(0), *p = dummy;
        dummy->next = head;
        
        prefixSum[0] = p;
        int cur = 0, tempCur = 0;
        while (p = p->next) {
            cur += p->val;
            if (prefixSum.find(cur) != prefixSum.end()) {
                ListNode* temp = prefixSum[cur]->next;
                prefixSum[cur]->next = p->next;
                tempCur = cur;
                // 还需要从 map 中删除消除区间的前缀和
                while (temp != p) {
                    tempCur += temp->val;
                    prefixSum.erase(tempCur);
                    temp = temp->next;
                }
                
            } else {
                prefixSum[cur] = p;
            }
        }
        
        return dummy->next;

    }
};


合并K个排序链表

https://leetcode-cn.com/problems/merge-k-sorted-lists/

合并两个链表的经验我们有,那么n个也是一样的,我们两个两个合并,如图所示:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        if(lists.empty()) return nullptr;
        int size = lists.size();
        if(size == 1) return lists[0];
        //两个两个合并
        int begin = 0;
        ListNode* Two = new ListNode();

        while(size>1&&begin<size)
        {
            if(begin == 0) {Two = List(lists[0],lists[1]);begin+=2;}
            else {Two = List(Two,lists[begin]);begin++;}
        }
        return Two;
    }
    ListNode* List(ListNode*& L1,ListNode*& L2)
    {
        if(!L1&&!L2) return nullptr;
        ListNode* head = nullptr;
        ListNode* Pre = head;
        while(L1&&L2)
        { 
            ListNode* Temp = new ListNode( );
            if(L1->val<=L2->val)
            {
                Temp->val = L1->val;
                L1 = L1->next;
            } 
            else
            {
                Temp->val = L2->val;
                L2 = L2->next;
            }
            if(head == nullptr) head = Temp;
            if(Pre == nullptr) Pre = Temp;
            else {Pre->next = Temp;Pre = Temp;}            
        }

        ListNode* After = L1 == nullptr?L2:L1;
        while(After)
        {
            ListNode* Temp = new ListNode(After->val);
            if(head == nullptr) head = Temp;
            if(Pre == nullptr) Pre = Temp;
            else {Pre->next = Temp;Pre = Temp;}     
            After = After->next;
        }
        return head!=nullptr?head:nullptr;
    }

};

显示太浪费了,我们采用典型的分治法的思想:

图源:https://leetcode-cn.com/problems/merge-k-sorted-lists/solution/he-bing-kge-pai-xu-lian-biao-by-leetcode-solutio-2/

那么我们程序要怎么写呢?利用递归,非常巧妙

    ListNode* merge(vector <ListNode*> &lists, int l, int r)
    {
        if (l == r) return lists[l];
        if (l > r) return nullptr;
        int mid = l+(r - l)/2;
        return List(merge(lists, l, mid), merge(lists, mid + 1, r));

    }

当起点终点一样的时候,我们返回原值,起点大于终点,我们返回空,下面我们看看这个递归过程是怎样的

优化时间,分治法完整代码:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        if(lists.empty()) return nullptr;
        int size = lists.size();
        if(size == 1) return lists[0];
        return merge(lists, 0, size - 1);
    }
    ListNode* merge(vector <ListNode*> &lists, int l, int r)
    {
        if (l == r) return lists[l];
        if (l > r) return nullptr;
        int mid = l+(r - l)/2;
        return List(merge(lists, l, mid), merge(lists, mid + 1, r));

    }
    ListNode* List(ListNode* L1,ListNode* L2)
    {
        if(!L1&&!L2) return nullptr;
        ListNode* head = nullptr;
        ListNode* Pre = head;
        while(L1&&L2)
        { 
            ListNode* Temp = new ListNode( );
            if(L1->val<=L2->val)
            {
                Temp->val = L1->val;
                L1 = L1->next;
            } 
            else
            {
                Temp->val = L2->val;
                L2 = L2->next;
            }
            if(head == nullptr) head = Temp;
            if(Pre == nullptr) Pre = Temp;
            else {Pre->next = Temp;Pre = Temp;}            
        }
        // cout<<head<<endl;

        ListNode* After = L1 == nullptr?L2:L1;
        // cout<<After->val<<endl;
        while(After)
        {
            ListNode* Temp = new ListNode(After->val);
            if(head == nullptr) head = Temp;
            if(Pre == nullptr) Pre = Temp;
            else {Pre->next = Temp;Pre = Temp;}     
            After = After->next;
        }
        return head!=nullptr?head:nullptr;
    }

};

但是每次都要分配空间,实在是不划算,我们原地合并两个链表:

迭代/递归:

class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        if(lists.empty()) return nullptr;
        int size = lists.size();
        if(size == 1) return lists[0];

        return merge(lists, 0, size - 1);
    }
    ListNode* merge(vector <ListNode*> &lists, int l, int r)
    {
        if (l == r) return lists[l];
        if (l > r) return nullptr;
        int mid = l+(r - l)/2;
        return List(merge(lists, l, mid), merge(lists, mid + 1, r));
    } 
    //递归
    ListNode* List(ListNode* l1,ListNode* l2)
    {
        if(!l1) return l2;
        else if(!l2) return l1;
        else if(l1->val<l2->val) {l1->next = List(l1->next,l2);return l1;}
        else  {l2->next = List(l1,l2->next);return l2;}
    }
    //迭代
    ListNode* List(ListNode* l1,ListNode* l2)
    {
        if(!l1||!l2) return (l1 == nullptr)?l2:l1;
        ListNode NewHead;
        ListNode* Pre = &NewHead;
        while(l1&&l2)
        {
            if(l1->val<l2->val) {Pre->next = l1;l1 = l1->next;}
            else {Pre->next = l2;l2 = l2->next;}
            Pre = Pre->next;
        }
        Pre->next = (l1 == nullptr)?l2:l1;
        return NewHead.next;
    }
    
};

还有更好的办法吗?当然有,使用优先队列:priority_queue <Status> q;

class Solution {
public:
    struct Status {
        int val;
        ListNode *ptr;
        bool operator < (const Status &rhs) const {
            return val > rhs.val;
        }
    };

    priority_queue <Status> q;

    ListNode* mergeKLists(vector<ListNode*>& lists) {
        for (auto node: lists) {
            if (node) q.push({node->val, node});
        }
        ListNode head, *tail = &head;
        while (!q.empty()) {
            auto f = q.top(); q.pop();
            tail->next = f.ptr; 
            tail = tail->next;
            if (f.ptr->next) q.push({f.ptr->next->val, f.ptr->next});
        }
        return head.next;
    }
};

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/merge-k-sorted-lists/solution/he-bing-kge-pai-xu-lian-biao-by-leetcode-solutio-2/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

 

算法参考:https://leetcode-cn.com/problems/merge-k-sorted-lists/solution/he-bing-kge-pai-xu-lian-biao-by-leetcode-solutio-2/

双向链表

struct DLinkedNode {
    int key, value;
    DLinkedNode* prev;//每个结点有两个指针,一个指向前,一个指向后
    DLinkedNode* next;
    //默认构造函数
    DLinkedNode(): key(0), value(0), prev(nullptr), next(nullptr) {}
    //构造函数
    DLinkedNode(int _key, int _value): key(_key), value(_value), prev(nullptr), next(nullptr) {}
};
class LRUCache {
public:
//本题的核心意图是,链表头结点的地方,是经常访问的字段,链表的尾部是不经常访问的字段
//利用Hash去快速定位链表中的点,以此达到O(1)
    unordered_map<int, DLinkedNode*> cache;//保留key值和链表结点
    DLinkedNode* head;//链表的虚拟头结点
    DLinkedNode* tail;//链表的虚拟尾结点
    int Size;//时刻统计链表的长度
    int Capacity;//真实的容器大小
    LRUCache(int capacity) {
        Capacity = capacity;//容器大小赋值
        Size = 0;//目前链表额大小
        //设置虚拟头结点和尾结点
        head = new DLinkedNode();
        tail = new DLinkedNode();
        head->next = tail;
        tail->prev = head;
    }
    int get(int key) {
        if (!cache.count(key)) {//没有找到
            return -1;
        }
        // 如果 key 存在,先通过哈希表定位O(1),再移到头部
        DLinkedNode* node = cache[key];
        moveToHead(node);//一旦有访问,就移动到链表的首部,表示已经访问,更新访问字段
        return node->value;
    }
    void put(int key, int value) {
        if (!cache.count(key)){
            DLinkedNode* node = new DLinkedNode(key, value);
            cache[key] = node;
            addToHead(node);
            ++Size;
            if (Size > Capacity){
                // 如果超出容量,删除双向链表的尾部节点
                DLinkedNode* removed = removeTail();//删除尾部结点
                // 删除哈希表中对应的项
                cache.erase(removed->key);
                delete removed;
                --Size;
            }
        }
        else {
            // 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部
            DLinkedNode* node = cache[key];
            node->value = value;
            moveToHead(node);
        }
    }
    //操作双向链表的相应内容
    //添加到头部
    void addToHead(DLinkedNode* node) {
        if(node == nullptr) return;
        node->prev = head;
        node->next = head->next;
        head->next->prev = node;
        head->next = node;
    }
    //移除结点
    void removeNode(DLinkedNode* node) {
        if(node == nullptr) return;
        node->prev->next = node->next;
        node->next->prev = node->prev;
    }
    //移除尾部结点
    DLinkedNode* removeTail() {
        DLinkedNode* node = tail->prev;
        removeNode(node);
        return node;
    }
    //首部增加结点
    void moveToHead(DLinkedNode* node) {
        removeNode(node);
        addToHead(node);
    }    

};

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值