链表相关基础知识及对应LC题目汇总

Posted by 谢玄xx on April 12, 2022

一、链表的基本性质

1.1 定义链表结构体

定义链表的结构体是必要的。虽然不定义的话,也会调用默认的构造函数,但这个默认构造函数无法赋初值。理由稍后解释,先来看结构体定义

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

定义了结构体后,如果我想定义新的节点,那么赋初值就很方便,如下所示:

ListNode* head = new ListNode(5);

使用默认构造函数初始化节点:

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

所以可以看出,如果不定义构造函数使用默认构造函数的话,在初始化的时候就不能直接给变量赋值!

1.2 添加节点

1.3 删除节点

  • 若想将A->B->C链表中的B节点删去,只要将A节点的next指针 指向C节点就可以了。那B节点不是依然存留在内存里么?只不过是没有在这个链表里而已。 答案是肯定的,所以在C++里最好是再手动释放这个D节点,释放这块内存。
  • 其他语言例如Java、Python,就有自己的内存回收机制,就不用自己手动释放了。

二、反转链表——绝对高频考题

2.1 题目描述

给定一个单链表的头节点head,请你反转这个链表并返回它。

2.2.1 解题思路1——递归

代码如下

ListNode* reverseList(ListNode* head)
{
    if(!head || !head->next)
    {
        return head;
    }
    ListNode* newhead = reverseList(head->next);
    head->next->next = head;
    head->next = nullptr;
    return newhead;
}

算法时间复杂度:O(n),其中n是链表长度;空间复杂度:O(n)。空间复杂度主要取决于递归所调用的栈空间,最多为n层。

2.2.2 解题思路2——迭代

假设链表为1-2-3-nullptr,我们需要把它反转为nullptr-3-2-1.遍历链表时,将当前节点的next指针改为指向前一个节点。由于节点没有引用其前一个节点,因此必须事先存储它的前一个节点。在更改引用之前,还需要存储后一个节点,最后返回新的头引用。

  • 先把前节点置空,把head赋给当前节点;

  • 然后next = prev,prev = curr, curr = next

  • 最后返回previous即可。

代码如下

ListNode* reverseList(ListNode* head)
{
    ListNode* prev = nullptr;
    ListNode* curr = head;
    while(curr)
    {
        ListNode* next = curr->next;
        curr->next = prev;
        prev = curr;
        curr = next;
    }
    return prev;
}

算法时间复杂度:O(n),其中n是链表长度;空间复杂度:O(1)。

三、合并两个有序链表

  • 将两个有序链表合并为一个新的升序链表并返回。

3.1.1 解题思路1——递归

*

代码如下

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;
    }    
}

时间复杂度为O(m + n),空间复杂度也为O(m + n).

3.1.2 解题思路2——

代码如下

四、删除链表中value为某一值的全部节点

4.1.1 解题思路1——直接删!

代码如下:

ListNode* removeElements(ListNode* head, int val) {
    // 删除头结点
    while (head != NULL && head->val == val) { // 注意这里不是if
        ListNode* tmp = head;
        head = head->next;
        delete tmp;
    }

    // 删除非头结点
    ListNode* cur = head;
    while (cur != NULL && cur->next!= NULL) {
        if (cur->next->val == val) {
            ListNode* tmp = cur->next;
            cur->next = cur->next->next;
            delete tmp;
        } else {
            cur = cur->next;
        }
    }
    return head;
}

4.1.2 解题思路2——虚拟头节点法

代码如下:

ListNode* removeElements(ListNode* head, int val) {
    ListNode* dummyHead = new ListNode(0); // 设置一个虚拟头结点
    dummyHead->next = head; // 将虚拟头结点指向head,这样方面后面做删除操作
    ListNode* cur = dummyHead;
    while (cur->next != NULL) {
        if(cur->next->val == val) {
            ListNode* tmp = cur->next;
            cur->next = cur->next->next;
            delete tmp;
        } else {
            cur = cur->next;
        }
    }
    head = dummyHead->next;
    delete dummyHead;
    return head;
}

五、两两交换链表中的节点

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

解题思路

  • 建议使用虚拟头结点,这样会方便很多,要不然每次针对头结点(没有前一个指针指向头结点),还要单独处理。

代码如下

ListNode* swapPairs(ListNode* head) {
    ListNode* dummyHead = new ListNode(0); // 设置一个虚拟头结点
    dummyHead->next = head; // 将虚拟头结点指向head,这样方面后面做删除操作
    ListNode* cur = dummyHead;
    while(cur->next != nullptr && cur->next->next != nullptr) {
        ListNode* tmp = cur->next; // 记录临时节点
        ListNode* tmp1 = cur->next->next->next; // 记录临时节点

        cur->next = cur->next->next;    // 步骤一
        cur->next->next = tmp;          // 步骤二
        cur->next->next->next = tmp1;   // 步骤三

        cur = cur->next->next; // cur移动两位,准备下一轮交换
    }
    return dummyHead->next;
}

六、LC第2题——两链表反向相加

给你两个非空的链表,表示两个非负的整数。它们每位数字都是按照逆序的方式存储的,并且每个节点只能存储一位数字。 请你将两个数相加,并以相同形式返回一个表示和的链表。注:本题可以假设除了数字 0 之外,这两个数都不会以 0开头。

解题思路

代码如下:

ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
    ListNode* dummy = new ListNode(0);
    ListNode* curr = dummy;
    int sum;
    while(l1 || l2 || sum) {
        if(l1) sum += l1->val, l1 = l1->next;
        if(l2) sum += l2->val, l2 = l2->next;
        curr->next = new ListNode(sum % 10);
        curr = curr->next;
        sum = sum / 10;

    }
    return dummy->next;
}