LeetCode 链表专题

这是整理的LeetCode上的链表专题,按照codeTop上的频率顺序从高到低进行整理。

操作链表节点位置类型

删除问题

剑指 Offer 18. 删除链表的节点

给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。

返回删除后的链表的头节点。

注意:此题对比原题有改动

示例 1:

输入: head = [4,5,1,9], val = 5
输出: [4,1,9]
解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.

class Solution {
public:
    ListNode* deleteNode(ListNode* head, int val) {
        ListNode* newHead = new ListNode(1);
        newHead->next = head;
        ListNode* p = newHead;
        while(p != nullptr && p->next != nullptr){
            if(p->next->val == val){
                p->next = p->next->next;
                break;
            }
            p = p->next;
        }
        return newHead->next;
    }
};

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

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

进阶:你能尝试使用一趟扫描实现吗?

示例 1:

在这里插入图片描述

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

双指针法

class Solution {
public:
    
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode *dummyHead = new ListNode(0, head);
        ListNode *p = dummyHead;
        ListNode *q = dummyHead;
        for(int i=0; i<n+1; i++){
            q = q->next;
        }
        while(q != nullptr){
            p = p->next;
            q = q->next;
        }
        ListNode *del = p->next;
        p->next = del->next;
        delete del;
        return dummyHead->next;
    }
};

83. 删除排序链表中的重复元素

存在一个按升序排列的链表,给你这个链表的头节点 head ,请你删除所有重复的元素,使每个元素 只出现一次 。

返回同样按升序排列的结果链表。

示例 1:
在这里插入图片描述

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

class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        ListNode *cur = head;
        while(cur){
            int curVal = cur->val;
            ListNode *p = cur->next;
            while(p != nullptr && p->val == curVal){
                cur->next = p->next;
                p = p->next;
            }
            cur = p;
        }
        return head;
    }
};

82. 删除排序链表中的重复元素 II

存在一个按升序排列的链表,给你这个链表的头节点 head ,请你删除链表中所有存在数字重复情况的节点,只保留原始链表中 没有重复出现 的数字。

返回同样按升序排列的结果链表。

示例 1:
在这里插入图片描述

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

class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        if (!head) {
            return head;
        }
        ListNode* dummy = new ListNode(0, head);
        ListNode* cur = dummy;
        while (cur->next && cur->next->next) {
            if (cur->next->val == cur->next->next->val) {
                int x = cur->next->val;
                while (cur->next && cur->next->val == x) {
                    cur->next = cur->next->next;
                }
            }
            else {
                cur = cur->next;
            }
        }
        return dummy->next;
    }
};

位置变换问题

206. 反转链表

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

示例 1:
在这里插入图片描述

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

这是最最最最常考的一题,一定一定要知道怎么做,且要非常熟悉。

  • 采用三指针法:前一个指针、当前指针、后一个指针
// 三指针法
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode *pre = nullptr; // 前一个指针
        ListNode *cur = head; // 当前指针
        while(cur){
            ListNode *u = cur->next; // 后一个指针
            cur->next = pre;
            pre = cur;
            cur = u;
        }
        return pre;
    }
};
  • 递归法:递归法通过从后往前进行反转。
    递归法不是很好理解。举个例子:对于[1,2,3,4,5]这一链表,我们走到最后5这个节点,返回5,即reverseList(4)时,p=5,然后4->next->next 即 5->next = 4, 4->next = nullptr, 返回p这个节点即5->4->nullptr,接下来执行reverseList(3)时,4->next = 3, 3->next = nullptr, 这时返回p这个节点即5->4->3->nullptr,以此类推直到reverseList(1),返回的就是5->4->3->2->1->nullptr。
// 递归
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if(head == nullptr || head->next == nullptr) return head;
        ListNode *p = reverseList(head->next);
        head->next->next = head;
        head->next = nullptr;
        return p;
    }
};

92. 反转链表 II

给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。

示例 1:
在这里插入图片描述

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

我们可以知道这题需要找到位置left的前一个节点,用pre指针指向left前一个节点,然后我们需要将位置right的节点下一个节点设为空,这样从left到right进行翻转,然后再链接起断开的位置即可。

class Solution {
public:
    ListNode* reverseBetween(ListNode* head, int left, int right) {
        ListNode *pre = new ListNode(0);
        pre->next = head;
        ListNode *newHead = pre;
        ListNode *start = head;
        ListNode *end = head;
        while(left > 1 || right > 1){
            if(left > 1){
                pre = pre->next;
                start = start->next;
                left--;
            }
            end = end->next;
            right--;
        }
        ListNode *nextNode = end->next;
        end->next = nullptr;
        pre->next = reverse(start);
        start->next = nextNode;
        return newHead->next;
    }
    ListNode *reverse(ListNode *node){
        ListNode *pre = nullptr;
        ListNode *cur = node;
        while(cur != nullptr ){
            ListNode *nextNode = cur->next;
            cur->next = pre;
            pre = cur;
            cur = nextNode;
        }
        return pre;
    }
};

25. K 个一组翻转链表

给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。

k 是一个正整数,它的值小于或等于链表的长度。

如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

进阶:

  • 你可以设计一个只使用常数额外空间的算法来解决此问题吗?
  • 你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

示例 1:
在这里插入图片描述

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

示例 3:

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

示例 4:

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

我们使用模拟来解决此题。即从链表的开始我们选择k个节点作为待翻转子链表,然后通过反转k个节点的链表这一方法(迭代或递归),然后继续对下面k个节点做翻转。

我们使用四个指针

  • pre 指向带翻转子区间的前驱
  • start 指向带翻转子区间的开始
  • end 指向带翻转子区间的末尾
  • u 指向带翻转子区间的后继

注意我们如果使用start指针指向带翻转子区间的开始,end指针指向区间末尾,那么通过翻转,start和end的位置也发生了改变,即下面的图
在这里插入图片描述

这样我们翻转完子区间后,pre的后继为end,
start的后继为u,pre到start的位置,end也到start的位置。

整个过程如下:
在这里插入图片描述

// 模拟
class Solution {
public:
    ListNode* reverseKGroup(ListNode* head, int k) {
        ListNode *newHead = new ListNode(0);
        newHead->next = head;
        ListNode *pre = newHead;
        ListNode *end = newHead;

        while(end->next != nullptr){
            for(int i=0; i<k && end!=nullptr; i++) end = end->next;
            if (end == nullptr) break;
            ListNode *start = pre->next;
            ListNode *u = end->next;
            end->next = nullptr;
            pre->next = reverse(start);
            start->next = u;
            pre = start;
            end = pre;
        }
        return newHead->next;
    }

    // 翻转一个区间的链表
    ListNode* reverse(ListNode *head){
        ListNode *pre = nullptr;
        ListNode *cur = head;
        while(cur != nullptr){
            ListNode *u = cur->next;
            cur->next = pre;
            pre = cur;
            cur = u;
        }
        return pre;
    }
};

143. 重排链表

给定一个单链表 L:L0→L1→…→Ln-1→Ln ,
将其重新排列后变为: L0→Ln→L1→Ln-1→L2→Ln-2→…

你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

示例 1:

给定链表 1->2->3->4, 重新排列为 1->4->2->3.

示例 2:

给定链表 1->2->3->4->5, 重新排列为 1->5->2->4->3.

class Solution {
public:
    void reorderList(ListNode* head) {
        if(!head) return;
        vector<ListNode*> vec;
        ListNode *p = head;
        while(p){
            vec.push_back(p);
            p = p->next;
        }
        int l = 0, r = vec.size()-1;
        while(l < r){
            vec[l]->next = vec[r];
            vec[r--]->next = vec[++l];
        }
        vec[l]->next = nullptr;
    }
};

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

给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。

你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

示例 1:
在这里插入图片描述

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

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        if(head == nullptr || head->next == nullptr){
            return head;
        }
        ListNode* newHead = head->next;
        head->next = swapPairs(newHead->next);
        newHead->next = head;
        return newHead;
    }
};

328. 奇偶链表

给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。

请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。

示例 1:

输入: 1->2->3->4->5->NULL
输出: 1->3->5->2->4->NULL

class Solution {
public:
    ListNode* oddEvenList(ListNode* head) {
        if (head == nullptr) return head;
        ListNode* evenHead = head->next;        
        ListNode* odd = head, *even = evenHead; 
        while (even && even->next)
        {
            odd->next = even->next;
            odd = odd->next;
            even->next = odd->next;
            even = even->next;
        }
        odd->next = evenHead;
        return head;
    }
};

环形链表题型

141. 环形链表

给定一个链表,判断链表中是否有环。

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

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

你能用 O(1)(即,常量)内存解决此问题吗?

示例 1:

在这里插入图片描述

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

使用快慢两指针进行判断,slow指针每次走1步,而fast指针每次走2步,这样如果fast指针遇到slow指针,则存在环,否则不存在。因为如果有环,当每走一轮,fast与slow指针之间的间距+1,最终fast一定会追上slow。

class Solution {
public:
    bool hasCycle(ListNode *head) {
        ListNode *fast = head;
        ListNode *slow = head;
        while(true){
            if(fast == nullptr || fast->next == nullptr) return false;
            fast = fast->next->next;
            slow = slow->next;
            if(fast == slow) return true;
        } 
        return false;
    }
};

142. 环形链表 II

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意,pos 仅仅是用于标识环的情况,并不会作为参数传递到函数中。

说明:不允许修改给定的链表。

进阶:

你是否可以使用 O(1) 空间解决此题?

示例 1:
在这里插入图片描述

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

这题与上一题的区别在于不仅仅是否有环,而且需要知道入环的第一个结点。

这里涉及到数学证明,证明如下:

设两指针:fast ( f f f):走2步;slow ( s s s):走1步。
设链表节点数: a + b a + b a+b,其中 a a a 为链表头部到环入口, b b b 为环节点数。
f = 2 s f = 2s f=2s
f = s + n b f = s + nb f=s+nb (如果相遇、fast一定是走了 s + nb 个结点数)
解得
f = 2 n b f = 2nb f=2nb
s = n b s = nb s=nb
即fast和slow分别走了 2 n 2n 2n, n n n个环的周长
我们知道如果走到入环第一个结点,需要步数为 a + n b a + nb a+nb,而第一次相遇slow走了 n b nb nb 步数,
让fast从链表头部开始(令 f = 0 f = 0 f=0),与slow(这时 s = n b s = nb s=nb)一起每次只走一步,这样fast走到 a a a 步时,slow走了 a + n b a + nb a+nb,即fast和slow在入环第一个结点相遇。

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode *fast = head;
        ListNode *slow = head;
        while(true){
            if(fast == nullptr || fast->next == nullptr ) return nullptr;
            slow = slow->next; 
            fast = fast->next->next;
            if(slow == fast) break;
        }
        fast = head;
        while(slow != fast){
            slow = slow->next;
            fast = fast->next;
        }
        return slow;
    }
};

两(多)个链表相关的问题

21. 合并两个有序链表

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

示例 1:
在这里插入图片描述

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

  • 迭代解决
// 迭代解决。
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        ListNode *newHead = new ListNode(0);
        ListNode *cur = new ListNode(0);
        newHead->next = cur;
        while(l1 != nullptr && l2 != nullptr){
            if(l1->val < l2->val){
                cur->next = l1;
                l1 = l1->next;
            }else{
                cur->next = l2;
                l2 = l2->next;
            }
            cur = cur->next;
        }
        if(l1 != nullptr){
            cur->next = l1;
        }
        if(l2 != nullptr){
            cur->next = l2;
        }
        return newHead->next->next;
    }
};
  • 递归解决
// 递归解决 , 递归定义merge操作:
// 1. list1[0] + merge(list1[1:], list2) (list1[0] < list2[0]) 
// 2. list2[0] + merge(list1, list2[1:]) (otherwise)
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        if(l1 == nullptr) return l2;
        if(l2 == nullptr) return l1;
        if(l1->val < l2->val){
            l1->next = mergeTwoLists(l1->next, l2);
            return l1;
        } 
        else{
            l2->next = mergeTwoLists(l1, l2->next);
            return l2;
        }
    }
};

23. 合并K个升序链表

给你一个链表数组,每个链表都已经按升序排列。

请你将所有链表合并到一个升序链表中,返回合并后的链表。

示例 1:

输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]
解释:链表数组如下:
[
1->4->5,
1->3->4,
2->6
]
将它们合并到一个有序链表中得到。
1->1->2->3->4->4->5->6

示例 2:

输入:lists = []
输出:[]

示例 3:

输入:lists = [[]]
输出:[]

// 最小堆(优先队列)
class Solution {
public:
    static bool myCmp(ListNode* l1, ListNode* l2) {
        return l1->val >= l2->val;
    }
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        ListNode *head = new ListNode(0);
        priority_queue<ListNode*, vector<ListNode*>, function<bool(ListNode*, ListNode*)>> pq(myCmp);
        for(int i=0; i<lists.size(); i++){
            if(lists[i] != nullptr){
                pq.push(lists[i]);
            }
        }
        ListNode *cur = head;
        while(!pq.empty()){
            ListNode *u = pq.top();
            pq.pop();
            cur->next = u;
            cur = cur->next;
            if(u->next != nullptr) pq.push(u->next);
        }
        return head->next;
    }
};

160. 相交链表

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

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

在这里插入图片描述

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

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

示例 1:
在这里插入图片描述

输入: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 个节点。

【题目分析】
本题有两种方法,第一种为哈希表,第二种采用双指针法,我们主要讲一下双指针法。具体做法是:
创建p和q两个指针分别指向headA和headB,每步操作需要同时更新指针和;

如果指针p不为空,则将指针移到下一个节点;如果指针q不为空,则将指针移到下一个节点。

如果指针p为空,则将指针p移到链表headB的头节点;如果指针q为空,则将指针移到链headA的头节点。

当指针p和q指向同一个节点或者都为空时,返回它们指向的节点或者nullptr。

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        if (headA == nullptr || headB == nullptr) return nullptr;
        ListNode *p = headA, *q = headB;
        while(p!=q){
            p = p==nullptr?headB:p->next;
            q = q==nullptr?headA:q->next;
        }
        return p;
    }
};

2. 两数相加

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。

请你将两个数相加,并以相同形式返回一个表示和的链表。

你可以假设除了数字 0 之外,这两个数都不会以 0 开头。

示例 1:
在这里插入图片描述

输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Rust 是一种现代的编程语言,特别适合处理内存安全和线程安全的代码。在 LeetCode 中,链表是经常出现的题目练习类型,Rust 语言也是一种非常适合处理链表的语言。接下来,本文将从 Rust 语言的特点、链表的定义和操作,以及 Rust 在 LeetCode链表题目的练习等几个方面进行介绍和讲解。 Rust 语言的特点: Rust 是一种现代化的高性能、系统级、功能强大的编程语言,旨在提高软件的可靠性和安全性。Rust 语言具有如下几个特点: 1. 内存安全性:Rust 语言支持内存安全性和原语级的并发,可以有效地预防内存泄漏,空悬指针以及数据竞争等问题,保证程序的稳定性和可靠性。 2. 高性能:Rust 语言采用了“零成本抽象化”的设计思想,具有 C/C++ 等传统高性能语言的速度和效率。 3. 静态类型检查:Rust 语言支持静态类型检查,可以在编译时检查类型错误,避免一些运行时错误。 链表的定义和操作: 链表是一种数据结构,由一个个节点组成,每个节点保存着数据,并指向下一个节点。链表的定义和操作如下: 1. 定义:链表是由节点组成的数据结构,每个节点包含一个数据元素和一个指向下一个节点的指针。 2. 操作:链表的常用操作包括插入、删除、查找等,其中,插入操作主要包括在链表首尾插入节点和在指定位置插入节点等,删除操作主要包括删除链表首尾节点和删除指定位置节点等,查找操作主要包括根据数据元素查找节点和根据指针查找节点等。 Rust 在 LeetCode链表题目的练习: 在 LeetCode 中,链表是常见的题目类型,而 Rust 语言也是一个非常适合练习链表题目的语言。在 Rust 中,我们可以定义结构体表示链表的节点,使用指针表示节点的指向关系,然后实现各种操作函数来处理链表操作。 例如,针对 LeetCode 中的链表题目,我们可以用 Rust 语言来编写解法,例如,反转链表,合并两个有序链表,删除链表中的重复元素等等,这样可以更好地熟悉 Rust 语言的使用和链表的操作,提高算法和编程能力。 总之,在 Rust 中处理链表是非常方便和高效的,而 LeetCode 中的练习也是一个非常好的机会,让我们更好地掌握 Rust 语言和链表数据结构的知识。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值