链表LeetCode题目总结C++

链表相关leetcode题目总结


持续更新中~
最近在总结重要的数据结构二叉树、链表、图、栈、队列的编程实现及LeetCode常见题目,这篇博客总结的是链表。
链表的数据结构较为简单,一个链表节点包含两个元素,节点的值和指向下一个节点的指针,借助leetcode上的定义为:

struct ListNode {
 	int val;
 	ListNode *next;
 	ListNode() : val(0), next(NULL) {}
 	ListNode(int x) : val(x), next(NULL) {}
 	ListNode(int x, ListNode *next) : val(x), next(next) {}
 };

我画了个图,演示了一个链表,实质是几个结构体ListNode串在一起,每个ListNode结构体包含两个部分,一个是节点的值,一个是指向下一个节点的指针,一个节点只能有一个后继指针,但是可以有多个指针指向它。
在这里插入图片描述链表的特点是只能向后访问链表节点,不能往前访问链表节点,因此在编程中,为了能够从链表的第一个元素开始访问链表,常常再定义一个头指针,指向链表的第一个链表节点,访问第一个节点的值(head->val),访问第一个节点的指针(head->next),如下图所示:
在这里插入图片描述

一个小贴士,C++11引入了新的指针空值nullptr,这是一个C++11的关键字,用来表示空指针,可以代替NULL来表示空指针,表示更加清晰明了。

1.翻转链表

1.1 翻转整个链表(迭代+递归)

LeetCode206.翻转链表
翻转整个链表的代码看着非常简单,但是每次到自己写的时候总是磕磕碰碰,在这里总结一下。
之前自己有几点总是理解得不是很清楚,这里明确以下:
1.用已有指针初始化另一个指针,例如下面的程序第3行(ListNode* cur=head;)含义是,cur指针和head指针是两个不同的指针(拥有独立的内存空间),但他们指向同一个节点。
2.将一个指向链表节点的指针赋值给另一个指向链表节点的指针,如下面的程序第7行( pre=cur;),含义是,pre指着指向的链表节点更新为cur指针指向的链表节点。
方法1:迭代
我画了个图演示了用迭代的方法翻转链表的这个过程:
在这里插入图片描述代码实现也比较简单:

ListNode* reverseList(ListNode* head) {
    ListNode* pre=NULL;
    ListNode* cur=head;
    while(cur){
        ListNode* past=cur->next;
        cur->next=pre;
        pre=cur;
        cur=past;
    }
    return pre;
}

方法2:递归
递归的思路相对难以理解,可以将链表简化,一层层理解,我将三个节点的链表反转的过程可以见下图所示,可以发现,递归的方法是从后往前翻转链表,而迭代是从前往后。
在这里插入图片描述

代码实现如下,注意每次递归的执行都是讲第二个节点指针指向第一个节点,再将第一个节点指空即可。

ListNode* reverseList(ListNode* head) {
        if (!head || !head->next) {
            return head;
        }
        ListNode* newHead = reverseList(head->next);
        head->next->next = head;//第二个节点的指针(本来指空)指向第一个节点
        head->next = NULL;//第一个节点指空
        return newHead;
    }

1.2 翻转链表的部分节点

LeetCode 92.反转链表2
这个题目要求我们只翻转链表中的部分节点,其他节点的位置保持不变,有个1.1的分析,这一题只需要找准第left个和第right个节点,将第left个节点到第right个节点反转,同时将头尾分别接上原来的链表,特殊情况时left=1,要着重考虑,过程如下图所示:
在这里插入图片描述
代码如下:

ListNode* reverseBetween(ListNode* head, int left, int right) {
        ListNode* newhead=head;//newhead是指向第left个节点的指针
        ListNode* benewhead=head;//benewhead是指向第left-1个节点的指针
        for(int i=0;i<left-2;i++)benewhead=benewhead->next;
        if(left==1)newhead=head;//left=1时,newhead和benewhead
        else newhead=benewhead->next;
			 ListNode* cur=newhead;
        ListNode* pre=NULL;
        for(int i=0;i<right-left+1;i++){
            ListNode* past=cur->next;
            cur->next=pre;
            pre=cur;
            cur=past;   
        }
        //中间段的头为pre节点,尾为
        if(left!=1)benewhead->next=pre;//接头
        newhead->next=cur;//接尾
        if(left!=1)return head;
        return pre;
        
    }

这样看上去代码有点冗余,其实在解决链表的问题时,有一种常用的小技巧就是在链表头部在接一个假的头结点,这样就不需要考虑left=1特殊情况,代码也更加清晰简洁。
添加一个假的头结点的代码写法:

ListNode *reverseBetween(ListNode *head, int left, int right) {
        ListNode *fakehead = new ListNode(-1);
        fakehead->next = head;
        ListNode *pre = fakehead;
        for (int i = 0; i < left - 1; i++) {
            pre = pre->next;
        }
        ListNode *cur = pre->next;
        ListNode *next;
        for (int i = 0; i < right - left; i++) {
            next = cur->next;
            cur->next = next->next;
            next->next = pre->next;
            pre->next = next;
        }
        return fakehead->next;
    }

1.3 2个一组翻转链表(递归+迭代)

LeetCode24.两两交换链表节点
题目的要求是不能改变节点的值,因此需要交换节点本身。常见的方法有迭代和递归两种。
首先介绍递归的方法。
我们可以参考1.1递归的思路,递归终止条件相同,递归函数的返回值应该是原链表的第二个节点。
在这里插入图片描述

ListNode* swapPairs(ListNode* head) {
        if(head==NULL||head->next==NULL)return head;//节点个数为0和1的特殊情况
        ListNode* newHead = head->next;
        head->next = swapPairs(newHead->next);
        newHead->next = head;
        return newHead;
    }

迭代的思路,这里用到了之前说的小技巧,添加一个假的头结点:

ListNode* swapPairs(ListNode* head) {
        ListNode* fakeHead = new ListNode(0);
        fakeHead->next = head;
        ListNode* temp = fakeHead;
        while (temp->next != nullptr && temp->next->next != nullptr) {
            ListNode* node1 = temp->next;
            ListNode* node2 = temp->next->next;
            temp->next = node2;
            node1->next = node2->next;
            node2->next = node1;
            temp = node1;
        }
        return fakeHead->next;
    }

1.4 k个一组翻转链表

LeetCode25.k个一组翻转链表

2.环形链表

2.1 判断链表是否有环

LeetCode141.环形链表
关于环形链表的问题,一直是链表中非常重要的部分,这里要说明一个非常常见的方法,就是快慢指针,快指针一次走两个节点,慢指针一次走一个节点。
以下图的链表为例,我们来说明如何判断链表是否有环。
在这里插入图片描述在这里插入图片描述若链表无环,快慢指针无法相遇;若链表有环,快慢指针一定相遇。在写代码时一定要注意的就是空节点的判断,只要有下一个节点,首先是要判断是否为空。
代码如下:

bool hasCycle(ListNode *head) {
    if(head==NULL||head->next==NULL)return false;
    ListNode *slow=head;
    ListNode *fast=head->next;
    while(slow!=fast){
        if(fast==NULL||fast->next==NULL)return false;//若无环,快指针一定先到链表末尾
        slow=slow->next;
        fast=fast->next->next;
    }
    return true;
}

2.2 找出含环链表的入环节点

LeetCode142.环形链表2
在上一题的数学基础上,解决这个问题会容易很多。
在这里插入图片描述
在这里插入图片描述代码如下:只是在上题的代码上增加了ptr和slow指针的循环相遇

ListNode *detectCycle(ListNode *head) {
    if(head==NULL||head->next==NULL)return NULL;
    ListNode *slow=head;
    ListNode *fast=head->next;
    while(slow!=fast){
        if(fast==NULL||fast->next==NULL)return NULL;
        slow=slow->next;
        fast=fast->next->next;
    }
    ListNode *ptr=head;
    slow=slow->next;//前往别忘了slow要先走一步,因为ptr在head说明已经走了一步
    while(ptr !=slow){
        ptr=ptr->next;
        slow=slow->next;
    }
    return ptr;
}

3.查找链表特定节点

3.找出两单链表的相交节点

LeetCode160.相交链表
这个题目的思路实际上是一个数学技巧,见下图:
在这里插入图片描述按照这个思路,代码是这样的(注意不管是哪个指针到达末尾时,都要主要返回表头节点):

ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
    if(headA==NULL||headB==NULL)return NULL;
    ListNode* ptrA=headA;
    ListNode* ptrB=headB;
    while(ptrA!=NULL&&ptrB!=NULL){//第一阶段,短链表到达链表尾部
        ptrA=ptrA->next;
        ptrB=ptrB->next;
    }      
    if(ptrA==NULL){//若是ptrA先到达链表尾部
        ptrA=headB;
        while(ptrB!=NULL){//ptrB后到达链表尾部
            ptrA=ptrA->next;
            ptrB=ptrB->next;
        } 
        ptrB=headA;
        while(ptrA!=ptrB){
            ptrA=ptrA->next;
            ptrB=ptrB->next;
        }         
    }
    else {//ptrB先到达链表尾部
        ptrB=headA;
        while(ptrA!=NULL){
            ptrA=ptrA->next;
            ptrB=ptrB->next;
        }
        ptrA=headB;
        while(ptrA!=ptrB){
            ptrA=ptrA->next;
            ptrB=ptrB->next;
        }                  
    }          
    return ptrA;
}

虽然思路清晰,但是代码写法比较冗余,将判断条件合并,实际上代码可以写的更加简洁优雅:

ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
    if(headA==NULL||headB==NULL)return NULL;
        ListNode* ptrA=headA;
        ListNode* ptrB=headB;
        while(ptrA!=ptrB){
            ptrA = (ptrA == NULL) ? headB : ptrA->next;
            ptrB = (ptrB == NULL) ? headA : ptrB->next;
        }
        return ptrA;
    }

3.2链表的倒数第k个节点

offer22.链表倒数第k个节点
在这里插入图片描述
有了上面的思路,代码实现就比较简单了:

ListNode* getKthFromEnd(ListNode* head, int k) {
    ListNode* fast=head;
    for(int i=0;i<k;i++)fast=fast->next;//fast此时位于第k+1个节点
    ListNode* slow=head;
    while(fast!=NULL){
        slow=slow->next;
        fast=fast->next;
    }
    return slow;
}

4.删除链表节点

4.1 删除链表的特定节点

237.删除链表中的节点
不同于一般的删除节点,这道题目要求不能从表头开始访问,只能从待删除的节点开始访问,因此没法真正删掉这个节点,只能让这个节点“变成”待删除节点的下一个节点,顺便把指向变成下一个节点的下一个节点。在这里插入图片描述

void deleteNode(ListNode* node) {
        node->val=node->next->val;
        node->next=node->next->next; 
    }

4.1 删除链表的确定值的节点

203.移除链表元素

ListNode* removeElements(ListNode* head, int val) {
        ListNode* newhead = new ListNode(0,head);
        ListNode* temp = newhead;
        while (temp->next != NULL) {
            if (temp->next->val == val) {
                temp->next = temp->next->next;
            } else {
                temp = temp->next;
            }
        }
        return newhead->next;
    }

4.3删除中间节点

LeetCode2095.删除链表的中间节点

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

5.链表排序

offer077.链表排序


6.合并有序链表

6.1合并两个有序链表

LeetCode21.合并两个有序链表

 ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        if (l1 == NULL) return l2;
        else if (l2 == NULL) 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;
        }
    }

6.2合并k个有序链表

23.合并k个升序链表

ListNode* mergeTwoLists(ListNode *a, ListNode *b) {
    if ((!a) || (!b)) return a ? a : b;
    ListNode head, *tail = &head, *aPtr = a, *bPtr = b;
    while (aPtr && bPtr) {
        if (aPtr->val < bPtr->val) {
            tail->next = aPtr; aPtr = aPtr->next;
        } else {
            tail->next = bPtr; bPtr = bPtr->next;
        }
        tail = tail->next;
    }
    tail->next = (aPtr ? aPtr : bPtr);
    return head.next;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值