数据结构>链表

Hello World, Hello Blog

Posted by LYF on March 10, 2025

介绍

链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表 中的指针链接次序实现的 。

链表的出现是为了弥补顺序表的不足而出现的,链表的特点就像一条大金链子,增删较方便。

声明

//SingleList单链表
typedef int DataType;

typedef struct SListNode
{
	DataType data;
	struct SListNode* next;

}SLNode;

//双向循环链表
typedef struct ListNode
{
	DataType data;
	struct ListNode* prev;
	struct ListNode* next;
	
}ListNode;

OJ

1.反转链表

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。img

算法思路:利用三个指针进行指向的修改,curNext记录结点cur下一个结点,prev用来记录结点cur上一个结点,修改当前结点cur指向prev,修改完cur指向后,prev指向当前cur结点,之后cur指向下一个结点,curNext指向下一个结点,依次遍历链表。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* reverseList(struct ListNode* head) {
    //0个结点或1个结点
    if(head==NULL||head->next==NULL)
        return head;
    //多个结点

    struct ListNode* prev = NULL,*cur = head,*curNext = cur->next;
    
    while(cur)
    {
        //修改当前结点指向
        cur->next = prev;
        //迭代链表
        prev = cur;
        cur = curNext;
        if(curNext)
            curNext = curNext->next;
        
    }
    
    return prev;

}

法二:
struct ListNode* reverseList(struct ListNode* head){

    // if(head==NULL||head->next==NULL)
    //     return head;
    //头插
    struct ListNode* newhead = NULL,*cur = head;
    while(cur)
    {
        struct ListNode* curNext = cur->next;
        
        cur->next = newhead;
        newhead=cur;
        cur=curNext;
        
    }
    return newhead;

}

2.移除链表元素

给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点

示例 1:

img

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode Node;
struct ListNode* removeElements(struct ListNode* head, int val) {
    Node* cur = head;
    Node* prev = NULL;
        
    while(cur)
    {
        if(cur->val==val)
        {
            //注意:第一个结点就是val
            if(prev==NULL)
            {
                head=cur->next;
                free(cur);
                cur=head;
            }
            else
            {
                prev->next=cur->next;
                free(cur);
                cur=prev->next;
            }
            
        }
        else
        {
            prev = cur;
            cur = cur->next;
        }
        
    }
    return head;
}

3.链表的中间结点

给你单链表的头结点 head ,请你找出并返回链表的中间结点。

如果有两个中间结点,则返回第二个中间结点。

示例 1:

img

输入:head = [1,2,3,4,5]
输出:[3,4,5]
解释:链表只有一个中间结点,值为 3 。

示例 2:

img

输入:head = [1,2,3,4,5,6]
输出:[4,5,6]
解释:该链表有两个中间结点,值分别为 3 和 4 ,返回第二个结点。
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
 typedef struct ListNode Node;
struct ListNode* middleNode(struct ListNode* head) {
    
    Node* slow=head,*fast = head;

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

}

4.输出链表中倒数第k个结点

实现一种算法,找出单向链表中倒数第 k 个节点。返回该节点的值。

注意:本题相对原题稍作改动

示例:

输入: 1->2->3->4->5 和 k = 2
输出: 4

说明:

给定的 k 保证是有效的。

算法思路:双指针思想,利用快慢指针的相位差为k,来进行遍历,初始状态快慢指针都指向头结点,然后让快指针先走k个结点,慢指针还在头结点,此时快慢指针刚好相差k个结点,之后快慢指针同步往下一个结点访问,当快指针遍历到NULL,慢指针刚好指向倒数第k个结点。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
 typedef struct ListNode Node;
int kthToLast(struct ListNode* head, int k) {
    
    Node* slow=head,*fast = head;
    //fast先走k位
    while(k--)
    {
        if(fast)
        {
            fast=fast->next;
        }
        
    }

    while(fast)
    {
        slow = slow->next;
        fast = fast->next;
    }
    return slow->val;

}

5.合并两个有序链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

示例 1:

img

输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]

示例 2:

输入:l1 = [], l2 = []
输出:[]

示例 3:

输入:l1 = [], l2 = [0]
输出:[0]

算法思路:首先取两个链表中较小的元素作为新链表的头节点,然后通过比较两个链表中较小的元素链接到新链表中,注意:带哨兵位的头结点,只是开辟了一个空间,不存放元素,返回的是头结点的下一个结点。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode Node;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
    
    if(list1==NULL)
        return list2;
    if(list2==NULL)
        return list1;
    
    //不带哨兵位的头节点
    // Node* head=NULL,*cur=NULL;
    // if((list1->val)<(list2->val))
    // {
    //     head = list1;
    //     list1=list1->next;
    // }
    // else
    // {
    //     head = list2;
    //     list2=list2->next;
    // }
    //  cur = head;

    //带哨兵位的头节点
    Node* head=(Node*)malloc(sizeof(Node));
    Node* cur = head;

    
    while(list1&&list2)
    {

        if(list1->val<list2->val)
        {
            cur->next=list1;
            list1=list1->next;
        }
        else
        {
            cur->next=list2;
            list2=list2->next;
        }
        cur=cur->next;
    }

    if(list1)
    {
        cur->next = list1;
    }
    if(list2)
    {
        cur->next = list2;
    }
    //带哨兵位,取哨兵位的下一个做头节点
    Node* realHead = head->next;
    free(head);
    return realHead;


}

6.链表分割

以给定值x为基准将链表分割成两部分,所有小于x的结点排在大于或等于x的结点之前给定一个链表的头指针 ListNode* head,请返回重新排列后的链表的头指针。注意:分割以后保持原来的数据顺序不变。

算法思路:通过两个带哨兵位的头结点,分别链接比给定值x小的元素给lessHead,大于等于x的元素存放在greaterHead中,最后把两个链表链接在一块。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode Node;
struct ListNode* partition(struct ListNode* head, int x) {

	//声明两个带哨兵位的头结点
	Node* lessHead = (Node*)malloc(sizeof(Node));
	Node* lessTail = lessHead;
	Node* greaterHead = (Node*)malloc(sizeof(Node));
    Node* greaterTail = greaterHead;
    lessHead->next = NULL;
    greaterHead->next = NULL;
	
	Node* cur = head;
	while (cur)
	{
		if (cur->val < x)
		{
			lessTail->next = cur;
			lessTail = cur;
		}
		else
		{
			greaterTail->next = cur;
			greaterTail = cur;
		}
		cur = cur->next;

	}

	lessTail->next = greaterHead->next;
    greaterTail->next = NULL;//注意:一定要把最后一个指向NULL,不然可能变成环
	free(greaterHead);
	head = lessHead->next;
	free(lessHead);

	return head;

}

7.回文链表

给定一个链表的 头节点 head 请判断其是否为回文链表。

如果一个链表是回文,那么链表节点序列从前往后看和从后往前看是相同的。

示例 1:

img

输入: head = [1,2,3,3,2,1]
输出: true

示例 2:

img

输入: head = [1,2]
输出: false

算法思路:利用快慢指针找到中间位置,然后将其在中间位置进行断链,慢指针的前驱置为NULL,拆分成两个链表,将后半段链表逆置后得到newHead,然后让head和newHead链表的元素进行一对一比较,有一个不相等返回false,都相等返回true。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode Node;
//链表逆置
struct ListNode* reverseLinked(struct ListNode* head)
{
    if(head==NULL|| head->next == NULL)
        return head;
    //头插
    Node* cur = head->next;
    head->next = NULL;
    Node* tail = cur->next;
    while(cur)
    {   
        cur->next = head;
        head = cur;
        cur = tail;
        //注意空指针
        if(tail)
        {
            tail = tail->next;
        }
            
    }

    return head;
    
}

bool isPalindrome(struct ListNode* head) {
    
    Node* slow,*fast;
    slow = fast = head;
    Node* prev = NULL;
    while(fast && fast->next)
    {
        prev = slow;
        slow = slow->next;
        fast = fast->next->next;
    }
    //断链 拆分两段
    if(prev)//注意空指针
    {
        prev->next = NULL;
    }
        
    //逆置后半段
    Node* newHead = reverseLinked(slow);

    Node* cur = head;
    while(cur)
    {
        if(cur->val == newHead->val)
        {
            newHead = newHead->next;
            cur = cur->next;
        }
        else
        {
            return false;
        }
    }
    return true;
}

8.相交链表

给定两个单链表的头节点 headAheadB ,请找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null

图示两个链表在节点 c1 开始相交

img

题目数据 保证 整个链式结构中不存在环。

注意,函数返回结果后,链表必须 保持其原始结构

示例 1:

img

输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at '8'
解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。
在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。

示例 2:

img

输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Intersected at '2'
解释:相交节点的值为 2 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。
在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。

示例 3:

img

输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。
由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
这两个链表不相交,因此返回 null 。

算法思想:首先找出两链表较长的一个longList,利用两链表长度的差值k,把longList向下移动k个结点,此时以longList和shortList为头指针的链表长度相等,遍历这两个链表一次比较结点的地址,第一次相等的结点就是两链表相交起始结点。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */

typedef struct ListNode Node;
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    //找出最长链表
    int lenA = 0,lenB = 0;

    Node *curA = headA, *curB = headB;
    while(curA)
    {
        curA = curA->next;
        lenA++;
    }
    while(curB)
    {
        curB = curB->next;
        lenB++;
    }
    
    Node* longList = headA;
    Node* shortList= headB;

    if(lenA<lenB)
    {
        longList = headB;
        shortList = headA;
    }
    //两链表长度差值
    int k = abs(lenA-lenB);
    while(k>0)
    {
        longList = longList->next;
        k--;
    }

    //此时两链表长度相等,比较结点地址
    while(longList)
    {
        if(longList == shortList)
        {
            return longList;
        }
        longList = longList->next;
        shortList = shortList->next;
    }
    return NULL;




/*
    //两个链表长度差值
    int k = abs(lenA-lenB);
    if(lenA>lenB)
    {
        while(k>0&&headA)
        {
            headA = headA->next;
            k--;
        }

    }else
    {
        while(k>0&&headB)
        {
            headB = headB->next;
            k--;
        }
    }

    while(headA)
    {
        if(headA==headB)
        {
            return headA;
        }
        headA = headA->next;
        headB = headB->next;
            
    }
    return NULL;
*/


}

9.环形链表

给你一个链表的头节点 head ,判断链表中是否有环。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。

如果链表中存在环 ,则返回 true 。 否则,返回 false

示例 1:

img

输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。

示例 2:

img

输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。

示例 3:

img

输入:head = [1], pos = -1
输出:false
解释:链表中没有环。

算法思路:利用快慢指针的原理,如果链表中有环,那么快指针以2步速度,去追慢指针以1步速度,最终快慢指针相遇就说明链表中有环,类似于在操场上跑步,你以2m/s速度同向去追一个1m/s的小伙伴,最终肯定能够在整数秒内追上。(注意:只能是2步速度去追,不然你们两个之间距离X不会为0)

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode Node;
bool hasCycle(struct ListNode *head) {
	//快慢指针
    Node* slow ,*fast;
    slow = fast = head;

    while(fast && fast->next)
    {
        

        fast = fast->next->next;
        slow = slow->next;
        //注意:开始slow和fast都是head
        if(slow == fast)
        {
            return true;            
        }

    }
    return false;
}

10.随机链表的复制

给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。

构造这个链表的 深拷贝。 深拷贝应该正好由 n全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点

例如,如果原链表中有 XY 两个节点,其中 X.random --> Y 。那么在复制链表中对应的两个节点 xy ,同样有 x.random --> y

返回复制链表的头节点。

用一个由 n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index] 表示:

  • val:一个表示 Node.val 的整数。
  • random_index:随机指针指向的节点索引(范围从 0n-1);如果不指向任何节点,则为 null

你的代码 接受原链表的头节点 head 作为传入参数。

示例 1:

img

输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]

示例 2:

img

输入:head = [[1,1],[2,1]]
输出:[[1,1],[2,1]]

算法思路:首先把复杂链表的val依次拷贝到每个结点后面链接的新结点中,然后找到每个原结点的random,random指向的下一个结点就是该拷贝结点random所指向的结点,依次进行拷贝结点random,最后把原链表和拷贝链表拆分,返回拷贝链表头指针copyHead。

/**
 * Definition for a Node.
 * struct Node {
 *     int val;
 *     struct Node *next;
 *     struct Node *random;
 * };
 */
typedef struct Node Node;
struct Node* copyRandomList(struct Node* head) {
	
    if(head == NULL)
        return NULL;

    Node* cur = head;
    Node* next = cur->next;
    //拷贝val
    while(cur)
    {
        
        Node* copy = (Node*)malloc(sizeof(Node));
        copy->val = cur->val;
        copy->next = next;
        copy->random = NULL;


        cur->next = copy;        
        cur = next;
        if(next)
            next= next->next;

    }   

    //拷贝random
    cur = head;
    Node* curCopy = cur->next;
    
    while(cur)
    {   


        if(cur->random)
            curCopy->random = cur->random->next;
        else
            curCopy->random = NULL;

        cur = curCopy->next;
        if(cur)
            curCopy = cur->next;

    }

    //拆分拷贝链表
    cur = head;
    Node* copyHead = cur->next;
    curCopy = cur->next;
    
    while(cur)
    {
        next = curCopy->next;
        cur->next = next;
        cur = next;
        
        if(next)
        {
            curCopy->next = next->next;
            curCopy = next->next;
        }
        
        
    }
    return copyHead;


}

11.对链表进行插入排序

给定单个链表的头 head ,使用 插入排序 对链表进行排序,并返回 排序后链表的头

插入排序 算法的步骤:

  1. 插入排序是迭代的,每次只移动一个元素,直到所有元素可以形成一个有序的输出列表。
  2. 每次迭代中,插入排序只从输入数据中移除一个待排序的元素,找到它在序列中适当的位置,并将其插入。
  3. 重复直到所有输入数据插入完为止。

下面是插入排序算法的一个图形示例。部分排序的列表(黑色)最初只包含列表中的第一个元素。每次迭代时,从输入数据中删除一个元素(红色),并就地插入已排序的列表中。

对链表进行插入排序。

img

示例 1:

img

输入: head = [4,2,1,3]
输出: [1,2,3,4]

示例 2:

img

输入: head = [-1,5,3,4,0]
输出: [-1,0,3,4,5]

算法思路:插入排序是在一段有序区间内进行插入新的结点,开始时第一个结点有序,判断下一个结点是头插还是尾插,然后在下一个结点就可能是头插,尾插,中间插,三种情况讨论,分清楚思路后进行迭代。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode Node;
struct ListNode* insertionSortList(struct ListNode* head) {
    
    if(head==NULL|| head->next == NULL)
        return head;
    //排序后的头结点
    
    // //注意:看下面这句代码。。。
    // Node* sortHead = head;
    // sortHead->next = NULL;

    Node* sortHead = head;
    Node* cur = head->next;
    sortHead->next = NULL;
    Node* next = NULL;

    //在有序的区间内进行插入
    //头插,中间插,尾插

    while(cur)
    {
        next = cur->next;
        //头插
        if(cur->val<=sortHead->val)
        {
            cur->next = sortHead;
            sortHead = cur;
        }
        else
        {
            //中间插  [2,4,6]  cur=3
            
            Node* sortCur = sortHead->next;
            Node* sortPrev = sortHead;
            while(sortCur)
            {
                if(cur->val<=sortCur->val)
                {
                    sortPrev->next = cur;
                    cur->next = sortCur;
                    break;//注意:插入成功后跳出循环
                }
                else
                {
                    sortPrev = sortCur;
                    sortCur = sortCur->next;
                }
            }

            //尾插
            if(sortCur == NULL)
            {
                sortPrev->next = cur;
                cur->next = NULL;
                // sortCur = cur;
                // sortCur->next = NULL;//注意尾插后下一个要置为NULL,不然会成环
            }
            
        }
        cur  = next;

    }
    return sortHead;

}

12.删除链表中重复元素II

给定一个已排序的链表的头 head删除原始链表中所有重复数字的节点,只留下不同的数字 。返回 已排序的链表

示例 1:

img

输入:head = [1,2,3,3,4,4,5]
输出:[1,2,5]

示例 2:

img

输入:head = [1,1,1,2,3]
输出:[2,3]

算法思路:利用快慢指针,当慢指针cur的值与快指针next的值不同时,prev记录cur前驱,cur等于next,next向下走一步;当慢指针cur的值与快指针next的值相同时,cur不动,next一直向下遍历,直到出现一个next所指的值与cur不相等时,删除cur到当前next指向之间所有的重复结点,然后cur指向next,next指针继续向下,prev指向cur使之链接,继续判断慢指针cur的值和快指针next的值是否相同,重复上述步骤进行迭代。直到next为NULL,链表遍历完跳出循环,返回头指针。(注意:头部和尾部细节处理,prev为空,next为空)

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode Node;
struct ListNode* deleteDuplicates(struct ListNode* head) {
    
    if(head==NULL|| head->next==NULL)
        return head;
	//快慢指针
    Node* cur = head;
    Node* next = cur->next;
    Node* prev = NULL;
	//遍历链表
    while(next)
    {
    	//前后指针不相等,prev cur next 三指针同时往下走
        if(cur->val != next->val)
        {
            
            prev = cur;
            cur = next;
            next = next->next;
        }
        else
        {
            //找到下一个不相等的结点  条件&&
            while(next && cur->val == next->val)
                next= next->next;
            
            
            //删除重复出现结点
            while(cur!=next)
            {
                Node* del = cur;
                cur = cur->next;
                free(del);

            }
            //细节处理,prev为空时
            if(prev)
                 prev->next = cur;
            else
                head = cur;
            //细节处理,next已经走到最后
            if(next)
                next = next->next;    


        }
        
    }
    return head;


}