2、链表相关

本文深入探讨了链表的基础知识,包括单链表、双链表和循环链表的结构。讲解了链表的存储方式,并分析了链表在内存中的分布特点。详细阐述了链表的删除和添加节点的常见操作,以及如何通过快慢指针解决相关问题。此外,还介绍了如何设计链表结构并实现增删改查功能,以及翻转链表、交换节点、删除倒数第n个节点和寻找链表相交节点的算法。文章通过LeetCode相关题目展示了具体的实现过程。
摘要由CSDN通过智能技术生成

上一节:1、数组相关

下一节:3、哈希表相关

链表

1、理论基础

链表的形式

单链表:
在这里插入图片描述
双链表:
在这里插入图片描述
循环链表:
在这里插入图片描述

链表的存储

数组是在内存中是连续分布的,但是链表在内存中可不是连续分布的。
链表是通过指针域的指针链接在内存中各个节点。
所以链表中的节点在内存中不是连续分布的 ,而是散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理。
在这里插入图片描述

链表的定义

C语言

struct ListNode {
	int val;
	/* 这里结构体ListNode还没有定义成功就使用了,原因和编译器有关,之后在说 */
	struct ListNode* next;
};

删除节点

在这里插入图片描述

添加节点

在这里插入图片描述

性能分析

在这里插入图片描述

2、删除节点(使用虚拟头节点)

leetcode 203. 移除链表元素

leetcode 203. 移除链表元素
题意:删除链表中等于给定值 val 的所有节点。
示例 1:

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

C/C++ 每次删除节点之后都要释放其内存,不然会造成内存泄漏(即此内存不在使用了,但是没有释放,导致接下来分配内存时无法使用此空间)
1、使用原来的链表进行遍历

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* removeElements(struct ListNode* head, int val){
    /* 找到第一个不为目标值的节点 */
    while (head != NULL && head->val == val) {
        head = head->next;
    }
    struct ListNode* pre = head;
    struct ListNode* cur = head;
    while (cur != NULL) {
        /* 判断节点是否为目标值 
         * 是,则将该节点删除
         */
        if (cur->val == val) {
            struct ListNode* tmp = cur;
            pre->next = cur->next;
            cur = cur->next;
            free(tmp);
        } else {    /* 不是,则更新pre和cur的值,进行下一次判断 */
            pre = cur;
            cur = cur->next;
        }
    }
    return head;
}

2、增加虚拟头结点进行遍历

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* removeElements(struct ListNode* head, int val){
    /* 定义虚拟头节点 */
    struct ListNode dummyHead;
    dummyHead.val = 0;
    dummyHead.next = head;
    /* 定义遍历节点cur */
    struct ListNode* cur = &dummyHead;
    while (cur->next != NULL) {
        if (cur->next->val == val) {
            struct ListNode* tmp = cur->next;
            cur->next = tmp->next;
            free(tmp);
        } else {
            cur = cur->next;
        }
    }
    return dummyHead.next;
}

3、设计链表 (增删改查)

leetcode 707. 设计链表

leetcode 707. 设计链表
在链表类中实现这些功能:

  • get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
  • addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
  • addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
  • addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
  • deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。

在定义链表时使用虚拟头结点,这样对真正头结点的操作和其他节点的·1操作可以保持一致

typedef struct MyLinkedList{
    int val;
    struct MyLinkedList* next;
} MyLinkedList;

/** Initialize your data structure here. */
MyLinkedList* myLinkedListCreate() {
    MyLinkedList* obj = (MyLinkedList *)malloc(sizeof(struct MyLinkedList));
    if (obj == NULL) {
        printf("myLinkedListCreate : malloc err !\n");
        return NULL;
    }
    memset(obj, 0, sizeof(struct MyLinkedList));
    return obj;
}

/** Get the value of the index-th node in the linked list. If the index is invalid, return -1. */
int myLinkedListGet(MyLinkedList* obj, int index) {
    int cnt = 0;
    MyLinkedList* tmp = obj->next;
    while (tmp) {
        if (cnt == index) {
            return tmp->val;
        }
        cnt++;
        tmp = tmp->next;
    }
    return -1;
}

/** Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list. */
void myLinkedListAddAtHead(MyLinkedList* obj, int val) {
    MyLinkedList* head = (MyLinkedList *)malloc(sizeof(struct MyLinkedList));
    if (head == NULL) {
        printf("myLinkedListAddAtHead : malloc err !\n");
        return;
    }
    memset(head, 0, sizeof(struct MyLinkedList));
    head->val = val;
    head->next = obj->next;
    obj->next = head;
}

/** Append a node of value val to the last element of the linked list. */
void myLinkedListAddAtTail(MyLinkedList* obj, int val) {
    MyLinkedList* tail = (MyLinkedList *)malloc(sizeof(struct MyLinkedList));
    if (tail == NULL) {
        printf("myLinkedListAddAtTail : malloc err !\n");
        return;
    }
    memset(tail, 0, sizeof(struct MyLinkedList));
    tail->val = val;
    tail->next = NULL;
    MyLinkedList* tmp = obj;
    while (tmp->next) {
        tmp = tmp->next;
    }
    tmp->next = tail;
}

/** Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted. */
void myLinkedListAddAtIndex(MyLinkedList* obj, int index, int val) {
    int cnt = 0;
    MyLinkedList* new_node = (MyLinkedList *)malloc(sizeof(struct MyLinkedList));
    if (new_node == NULL) {
        printf("myLinkedListAddAtIndex : malloc err !\n");
        return;
    }
    memset(new_node, 0, sizeof(struct MyLinkedList));
    new_node->val = val;

    MyLinkedList* tmp = obj;
    while (tmp) {
        if (cnt == index) {
            break;
        }
        cnt++;
        tmp = tmp->next;
    }
    new_node->next = tmp->next;
    tmp->next = new_node;
}

/** Delete the index-th node in the linked list, if the index is valid. */
void myLinkedListDeleteAtIndex(MyLinkedList* obj, int index) {
    int cnt = 0;
    MyLinkedList* del_node = NULL;
    MyLinkedList* tmp = obj;

    while (tmp) {
        if (cnt == index) {
            break;
        }
        cnt++;
        tmp = tmp->next;
    }
    if (tmp->next == NULL) {
        return;
    }
    del_node = tmp->next;
    tmp->next = del_node->next;
    free(del_node);
}

void myLinkedListFree(MyLinkedList* obj) {
    MyLinkedList* tmp = obj->next;
    MyLinkedList* node_free = NULL;
    while (tmp) {
        node_free = tmp;
        tmp = tmp->next;
        free(node_free);
    }
    free(obj);
}

4、翻转链表(双指针)

leetcode 206.反转链表

leetcode 206.反转链表
题意:反转一个单链表。
示例: 输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL

使用双指针将当前头结点变为尾节点,尾节点变为头结点即可

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* reverseList(struct ListNode* head){
    struct ListNode* cur = head;
    struct ListNode* pre = NULL;
    while (cur) {
        struct ListNode* tmp = cur->next;
        cur->next = pre;
        pre = cur;
        cur = tmp;
    }
    return pre;
}

5、交换链表中的两节点(模拟过程)

leetcode 24. 两两交换链表中的节点

leetcode 24. 两两交换链表中的节点
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

模拟过程如下:
在这里插入图片描述
代码如下:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* swapPairs(struct ListNode* head) {
    /* 定义虚拟头结点 */
    struct ListNode* dummyHead = (struct ListNode*)malloc(sizeof(struct ListNode));
    dummyHead->val = 0;
    dummyHead->next = head;
    /* 用于遍历的当前节点 */
    struct ListNode* cur = dummyHead;
    while(cur->next != NULL && cur->next->next != NULL) {
        /* 记录当前的第一、三个节点 */
        struct ListNode* tmp = cur->next;
        struct ListNode* tmp1 = cur->next->next->next;
        /* 进行交换 */
        cur->next = cur->next->next;
        cur->next->next = tmp;
        cur->next->next->next = tmp1;
        /* 更新当前节点cur */
        cur = cur->next->next;
    }
    /* 返回值是虚拟头节点的next */
    return dummyHead->next;
}

6、删除倒数第n个节点(快慢指针)

leetcode 19. 删除链表的倒数第 N 个结点

leetcode 19. 删除链表的倒数第 N 个结点
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
进阶:你能尝试使用一趟扫描实现吗?

  • 示例1: 输入:head = [1,2,3,4,5], n = 2 输出:[1,2,3,5]
  • 示例 2:输入:head = [1], n = 1 输出:[]
  • 示例 3:输入:head = [1,2], n = 1 输出:[1]

思路

  • 快慢指针,fast先移动 n + 1步,再同时移动fast、slow直到fast为空;
  • 这样slow就移动到要删除节点的上一节点,就可以进行删除操作
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* removeNthFromEnd(struct ListNode* head, int n) {
    struct ListNode* dummyHead = (struct ListNode*)malloc(sizeof(struct ListNode));
    dummyHead->val = 0;
    dummyHead->next = head;
    struct ListNode* fast = dummyHead;
    struct ListNode* slow = dummyHead;
    while (n-- && fast != NULL) {
        fast = fast->next;
    }
    fast = fast->next;
    while (fast != NULL) {
        fast = fast->next;
        slow = slow->next;
    }
    struct ListNode* tmp = slow->next;
    slow->next = tmp->next;
    free(tmp);
    return dummyHead->next;
}

7、链表相交的第一个节点

leetcode 剑指 Offer 52. 两个链表的第一个公共节点

leetcode 剑指 Offer 52. 两个链表的第一个公共节点
在这里插入图片描述
A长度为a;B长度为b;公共长度为c
则:a - (a - c) == b - (b - c)
那么:a + (b - c) = b + (a - c)
即从头开始遍历两链表,a走到头走b;b走到头走a;最终相遇到相交节点上返回即可。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    struct ListNode *A = headA, *B = headB;
    while (A != B) {
        A = A != NULL ? A->next : headB;
        B = B != NULL ? B->next : headA;
    }
    return A;
}

8、环的入口(快慢指针经典题目)

leetcode 142. 环形链表 II

leetcode 142. 环形链表 II
题意: 给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
为了表示给定链表中的环,使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。
说明:不允许修改给定的链表。
在这里插入图片描述
代码如下:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode *detectCycle(struct ListNode *head) {
    /* 定义快慢指针 */
    struct ListNode *fast, *slow;
    /* 都指向表头结点 */
    fast = slow = head;
    while (fast && fast->next) {
        slow= slow->next;
        fast = fast->next->next;
        /* 因为快指针移动速度是慢指针的两倍
         * 所以当进入环中,快指针一定会赶上慢指针
         */
        if (fast == slow) {
            break;
        }
    }
    /* 若fast为空、fast->next为空,说明没有环 */
    if (fast == NULL || fast->next == NULL) {
        return NULL;
    }
    /* 上述if不成立,则表示有环
     * 将慢指针slow移动到表头head 
     */
    slow = head;
    /* 快慢指针同步移动,一定会在环的起点相遇 */
    while (slow != fast) {
        fast = fast->next;
        slow = slow->next;
    }
    return slow;
}

9、链表总结

  • 链表的种类主要为:单链表,双链表,循环链表
  • 链表的存储方式:链表的节点在内存中是分散存储的,通过指针连在一起。
  • 链表是如何进行增删改查的。
  • 数组和链表在不同场景下的性能分析。

上一节:1、数组相关

下一节:3、哈希表相关

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

「已注销」

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值