【链表】数据结构与算法——代码随想录

学习目的
1.会链表的定义、常用的基本操作。
2.会解决几种常用的链表相关的题目。

(一)链表基础知识
1.概念基础
(1)组成:数据+指针
(2)类型:单链表、双链表、循环链表
(3)存储方式:分散存储
2.代码实现
(1)节点定义

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

(2)节点初始化
初始化节点:
使用默认构造函数初始化节点时,默认构造函数不会初始化头结点。

ListNode*head = new ListNode()
head->val = 5;

(3)虚拟头结点

//设置一个虚拟的头结点
ListNode* dummyHead = new ListNode(0);
//让头结点与head发生联系
dummyHead->next = head;
//定义一个新的节点,对现有链表进行操作
ListNode* cur = dummyHead;
...//之间是删除添加等一些基本操作
//把头结点还给头结点
head = dummyHead->next;
//删除虚拟结点
delete dummyHead;
return head;

3.常用操作
(1)删除结点
(2)添加结点

(二)链表例题
1.移除链表中的元素
移除链表中的元素
题目梳理:删除满足某一个值的所有结点
步骤:
a.设置虚拟头结点(考察结点的创建,初始值以及下一个指针的指向)【在代码的末端同步写一个删除头结点】
b.遍历列表(链表中结点具体值的读取、while 循环的编写)
c.删除符合条件的结点(链表结点的删除)
运行时出现的错误以及解决:LeetCode:执行出错runtime error: member access within null pointer of type ‘ListNode‘ (solution.cpp)解决方法

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        ListNode* dummyHead = new ListNode(0);
        dummyHead->next = head;
        ListNode* cur = dummyHead;
        while (cur!=NULL&&cur->next != NULL){//需要有一个这样的判断条件通常
            if(cur->next->val == val){
                ListNode* tempt = cur->next;
                cur->next=cur->next->next;
                delete tempt;
            }else{
                cur = cur->next;
            }
        }
        head = dummyHead->next;
        delete dummyHead;
        return head;
    }
};

总结:写if时需要把条件满足时执行的语句和条件不满足执行的语句都写清楚
2.翻转链表
翻转链表
反转一个单链表
步骤:
因为反转过程中需要把链表变成两节,所以需要两个指针才能反转链表。
a.定义两个指针指向链表的前两个节点
b.按顺序依次更改链表指针的方向(cur要从头结点开始,pre先定义为空指针,属于对边界条件的考虑不够准确)
c.循环结束的标志是链表末尾的空指针

//双指针法,递归法
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* cur = head;//注意边界条件,一开始把pre初始化为头结点,导致反转后的链表没有尾巴的空指针
        ListNode* tempt1; //临时存储前一个结点的指向
        ListNode* pre = NULL;//注意初始化
        while(cur){
            tempt1 = cur->next;//为了不丢失后一个节点的信息
            cur->next = pre;//将cur结点调整了指向位置
            pre = cur;//把当前结点变成过去式
            cur=tempt1;//对下一个结点进行操作
        }
        return pre;//注意事return pre,return cur就会指向空处,不是一个正确的链表头
    }
};
class Solution {
public:
    ListNode* reverse(ListNode*pre,ListNode*cur){//递归第一步,确认输入变量
        if(cur==NULL) return pre;//递归的终止条件
        //不满足终止条件的交换指向
        ListNode* tempt;//交换问题一般都会有一个中间装载容器
        tempt = cur->next;//交换指针指向
        cur->next = pre;
        return reverse(cur,tempt);//单层递归逻辑

    }
    ListNode* reverseList(ListNode* head) {
        return reverse(NULL,head);
    }
};

3.两两交换链表中的节点
两两交换链表中的节点
借助第三个结点来完成结点的两两交换,多个指针的交换顺序非常重要。
步骤:
a.定义一个虚拟的头接点,最后记得删除头结点
b.定义两个临时节点,保证两个元素,和后部分的元素的头结点
c.两个元素之间进行的是类似反转链表的操作
d.遍历整个链表,用一个指针每一步走两个节点
报错出现超出时间限制时注意去找循环的问题
指针使用关键,用之前先进行存储。

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        ListNode* dummyHead = new ListNode(0);
        dummyHead->next = head;
        ListNode*cur = dummyHead;

        while(cur->next!=NULL&&cur->next->next!=NULL){
            ListNode* tempt1;
            tempt1 = cur->next;//为了防止头结点丢失,所以在交换时一定要注意存储头结点这操作,存起来就对这个指针指向进行操作
            cur->next = cur->next->next;
            ListNode* tempt2;
            tempt2 = cur->next->next;
            cur->next->next = tempt1;
            tempt1->next = tempt2;
            cur = cur->next->next;
        }
        head = dummyHead->next;
        delete dummyHead;
        return head;
    }
};

在这里插入图片描述
在这里插入图片描述

4.删除链表的倒数第N个点
删除链表的倒数第N个点
链表的最末尾是空指针,用此来判断满指针此时指向的是倒数N个。
删除元素+倒数第N个点的定位
步骤:
a.定义一个头结点定义,fast和slow两个指针
b.先将fast指针移动n+1步
c.同时移动快指针和慢指针,知道快指针到了末尾
此时慢指针指向的下一个结点就是待删除的元素。
d.删除元素的操作。

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dummyHead = new ListNode(0);
        dummyHead->next = head;
        ListNode* slow = dummyHead;
        ListNode* fast = dummyHead;

        while(n--){//让快指针先走N+1步,果然要自己亲自画才能体会到N+1步的妙处,删除结点指向的下一个元素是相对容易的。
            fast = fast->next;
        }
        fast = fast->next;
        while(fast!=NULL){//两个指针一直向后移动
            slow = slow->next;
            fast = fast->next;
        }
        //删除此时slow->next指针所指元素
        ListNode*tempt;
        tempt = slow->next;
        slow->next = tempt->next;
        delete tempt;
        head = dummyHead->next;
        delete dummyHead;
        return head;
    }
};

5.返回两个链表相交的起始点
相交结点不是用数值相等判断而是用指针相同进行判断。
步骤:
a.求出两个链表的长度,求出两个链表长度的差值
b.让curA移动到和curB末尾对其的的位置
两个链表相交后后面的链表是共用的。
c.比较curA和curB是否相同
相同返回交点,
不相同则返回空指针。

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        //定义可以操作的链表头是常用的处理方式
        ListNode* curA = headA;
        ListNode* curB = headB;
        //定义链表长度
        int lenthA = 0;
        int lenthB = 0;
        //求出每个链表的长度,没有专门的计算长度的函数,所以用遍历循环的方法进行计数
        while(curA!=NULL){
            lenthA++;
            curA=curA->next;
        }
        while(curB!=NULL){
            lenthB++;
            curB=curB->next;
        }
        //此时curA和curB的指向发生了变化,需要重新调整回去
        curA = headA;
        curB = headB;
        //对A,B链表的长度进行处理
        if(lenthB>lenthA){
            swap(lenthA,lenthB);
            swap(curA,curB);
        }
        int gap = lenthA-lenthB;
        while(gap--){
            curA = curA->next;
        }
        while(curA!=NULL){
            if(curA==curB){
                return curA;
            }
            curA = curA->next;
            curB = curB->next;
        }
        return NULL;    
    }
};

6.环形链表
判断链表中是否有环,环的入口;
环形链表的应用:解决约瑟夫环问题
已知 n 个人(以编号 1,2,3,…,n 分别表示)围坐在一张圆桌周围,从编号为 k 的人开始顺时针报数,数到 m 的那个人出列;他的下一个人又从 1 还是顺时针开始报数,数到 m 的那个人又出列;依次重复下去,要求找到最后出列的那个人。

环的入口,需要画图理解相关的数学知识
步骤:
a.定义快慢指针
b.快慢指针如果相遇,则链表中存在环
在相遇的位置定义一个新指针1,再在头结点处定义一个新指针2,
两个指针相遇的位置一定是在环的入口处,可以从公式的角度理解这一层关系。

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        //定义两个指针
        ListNode* fast = head;
        ListNode* slow = head;
        while(fast != NULL && fast->next != NULL) {
            fast = fast->next->next;
            slow = slow->next;
            if(fast==slow){
                ListNode* index1 = fast;
                ListNode* index2 = head;
                while(index1!=index2){//这里的条件判断不能用if...else...,它只执行一次,while则会一直循环
                    index1 = index1->next;
                    index2 = index2->next;                    
                }
                return index2;
            }
        }
        return NULL;    
    }
};

7.删除链表中有序链表中的重复元素
删除链表中有序链表中的重复元素
解题思路:有点像双指针

class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        ListNode* dummy=new ListNode(-1);
        ListNode* slow=dummy,* fast=head;
        dummy->next=head;

        while(fast && fast->next)
        {
        //通过fast找到不再重复的结点
            if(fast->val!=fast->next->val)
            {
            //如果fast是重复的结点
                if(slow->next!=fast) slow->next=fast->next;
                else slow=fast;
            }
            fast=fast->next;
        }
        //在链表结尾的处理
        if(slow->next!=fast) slow->next=fast->next;
        return dummy->next;
    }
};