算法<C++>——双指针操作链表

21. 合并两个有序链表 | 力扣 | LeetCode | 【easy】

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

示例 1:

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

输入:l1 = [], l2 = [] 输出:[] 示例 3:

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

两个链表的节点数目范围是 [0, 50]

-100 <= Node.val <= 100 l1 和 l2 均按 非递减顺序 排列 题目来源:力扣 21. 合并两个有序链表。

我们的 while 循环每次比较 p1 和 p2 的大小,把较小的节点接到结果链表上

迭代

cpp 复制代码
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        //虚拟头结点,更加便利的返回合并后的链表
        ListNode* preHead = new ListNode(-1);
        //维护prev指针
        ListNode* prev = preHead;
        while (l1 != nullptr && l2 != nullptr) {
            if (l1->val < l2->val) {
                prev->next = l1;
                l1 = l1->next;
            } else {
                prev->next = l2;
                l2 = l2->next;
            }
            prev = prev->next;
        }
        // 合并后 l1 和 l2 最多只有一个还未被合并完,我们直接将链表末尾指向未合并完的链表即可
        if(l1==nullptr) prev->next=l2;
        else prev->next=l1;

        return preHead->next;
    }
};

递归

cpp 复制代码
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        if(list1==nullptr)
        return list2;
        if(list2==nullptr)
        return list1;
        if(list1->val<=list2->val){
            list1->next = mergeTwoLists(list1->next,list2);
            return list1;
        }
        else{
            list2->next = mergeTwoLists(list1,list2->next);
            return list2;
        }
    }
};

86. 分隔链表 | 力扣 | LeetCode | 【medium】

给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。

你应当 保留 两个分区中每个节点的初始相对位置。

示例 1:

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

输入:head = [2,1], x = 2 输出:[1,2] 提示:

链表中节点的数目在范围 [0, 200] 内

-100 <= Node.val <= 100

-200 <= x <= 200 题目来源:力扣 86. 分隔链表

尝试一下

cpp 复制代码
class Solution {
public:
    ListNode* partition(ListNode* head, int x) {
        //初始化两个虚拟头结点分别代表大于val和小于val
        ListNode s(-1),l(-1);
        ListNode* sL = &s;
        ListNode* lL = &l;
        while(head){
            if(head->val<x){
                sL->next=head;
                sL=sL->next;
            }
            else{
                lL->next=head;
                lL=lL->next;
            }
            head=head->next;
        }
        //防止回环
        lL->next=nullptr;
        访问头结点
        sL->next=l.next;
        return s.next;
    }
};

//写法二
class Solution {
public:
    ListNode* partition(ListNode* head, int x) {
        // 存放小于 x 的链表的虚拟头结点
        ListNode* dummy1 = new ListNode(-1);
        // 存放大于等于 x 的链表的虚拟头结点
        ListNode* dummy2 = new ListNode(-1);
        // p1, p2 指针负责生成结果链表
        ListNode* p1 = dummy1, *p2 = dummy2;
        // p 负责遍历原链表,类似合并两个有序链表的逻辑
        // 这里是将一个链表分解成两个链表
        ListNode* p = head;
        while (p != nullptr) {
            if (p->val >= x) {
                p2->next = p;
                p2 = p2->next;
            } else {
                p1->next = p;
                p1 = p1->next;
            }
            // 不能直接让 p 指针前进,
            // p = p->next
            // 断开原链表中的每个节点的 next 指针
            ListNode* temp = p->next;
            p->next = nullptr;
            p = temp;
        }
        // 连接两个链表
        p1->next = dummy2->next;

        return dummy1->next;
    }
};

很好思路一样,但是在.和->使用出错

什么时候使用->或者什么时候使用.?

你问得好,关键在于:什么时候用 .,什么时候用 ->


✅ 一句话总结:

  • .(点):用于访问「对象本身」的成员。
  • ->(箭头):用于访问「指针所指对象」的成员。

📘 举个例子:

cpp 复制代码
ListNode node;      // 这是一个对象
ListNode* p = &node; // 这是一个指针
  • 如果是对象:

    cpp 复制代码
    node.val;      // ✅ 用 . 访问成员
    node.next;     // ✅ 用 . 访问成员
  • 如果是指针:

    cpp 复制代码
    p->val;        // ✅ 用 -> 访问成员
    p->next;       // ✅ 用 -> 访问成员
    (*p).val;      // ✅ 这是等价写法,但繁琐

💡 原理解释:

p->val 等价于 (*p).val

也就是说:

箭头 -> 是语法糖(语法简化),表示"先解引用,再访问成员"。


⚠️ 在你的代码里:

head 是一个指针(ListNode*),

所以必须用:

cpp 复制代码
head->val
head->next

如果写成 head.val,编译器会报错:

"request for member 'val' in pointer type ..."


✅ 小总结:

变量类型 访问成员写法 示例
对象(非指针) . node.val
指针(指向对象) -> head->val

所以在链表题中(比如 LeetCode 的大多数链表题),

你几乎总是操作指针,自然就得用 ->

23. 合并 K 个升序链表 | 力扣 | LeetCode | (hard)

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

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

示例 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 = [[]] 输出:[] 提示:

k == lists.length 0 <= k <= 10^4 0 <= lists[i].length <= 500

-10^4 <= lists[i][j] <= 10^4 lists[i] 按 升序 排列 lists[i].length 的总和不超过 10^4 题目来源:力扣 23. 合并 K 个升序链表。

合并两个链表的扩展

尝试使用递归:

cpp 复制代码
class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        //初始化为空
        ListNode *head=nullptr;
        for(int i=0;i<lists.size();i++){
            head=mergeTwoLists(head,lists[i]);
        }
        return head;
    }
    ListNode* mergeTwoLists(ListNode *a,ListNode *b){
        if(a==nullptr) return b;
        if(b==nullptr) return a;
        ListNode res;
        ListNode *prev = &res;
        ListNode *aL=a,*bL=b;
        while(aL&&bL){
            if(aL->val<bL->val){
                prev->next=aL;
                aL=aL->next;
            }
            else{
                prev->next=bL;
                bL=bL->next;
            }
            prev = prev->next;
        }
        prev->next=(aL?aL:bL);
        return res.next;
    }
};

尝试成功,看了题解......优化的方法暂时看不懂

141. 环形链表 | 力扣 | LeetCode | (easy)

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

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

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

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

判断链表是否包含环属于经典问题了,解决方案也是用快慢指针:

每当慢指针 slow 前进一步,快指针 fast 就前进两步。

如果 fast 最终能正常走到链表末尾,说明链表中没有环;如果 fast 走着走着竟然和 slow 相遇了,那肯定是 fast 在链表中转圈了,说明链表中含有环。

cpp 复制代码
class Solution {
public:
    bool hasCycle(ListNode* head) {
        if (head == NULL || head->next == NULL)
            return false;
        ListNode* slow = head;
        ListNode* fast = head->next;
        while (slow != fast) {
            if (fast == NULL || fast->next == NULL) {
                return false;
            }
            slow = slow->next;
            fast = fast->next->next;
        }
        return true;
    }
};

142. 环形链表(||) | 力扣 | LeetCode | (medium)

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

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos

来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos

不作为参数进行传递,仅仅是为了标识链表的实际情况。

不允许修改 链表。

输入:head = [3,2,0,-4], pos = 1

输出:返回索引为 1 的链表节点

解释:链表中有一个环,其尾部连接到第二个节点。

尝试用哈希集合统计已经遍历过的结点,一旦重复遇到,则即刻成环,输出索引

cpp 复制代码
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        unordered_set<ListNode *>L;
        while(head){
            if(L.count(head)){
                return head;
            }
            else{
                L.insert(head);
                head=head->next;
            }
        }
        return nullptr;
    }
};

双(快慢)指针优化后:

cpp 复制代码
class Solution {
public:
    ListNode* detectCycle(ListNode* head) {
        ListNode *s = head, *f = head, *res = head;
        while (f && f->next) {
            s = s->next;
            f = f->next->next;
            if (s == f) {
                while (res != s) {
                    res = res->next;
                    s = s->next;
                }
                return res;
            }
        }
        return nullptr;
    }
};

根据题意,任意时刻,fast 指针走过的距离都为 slow 指针的 2 倍。因此,我们有

a+(n+1)b+nc=2(a+b)⟹a=c+(n−1)(b+c) 有了 a=c+(n−1)(b+c)

的等量关系,我们会发现:从相遇点到入环点的距离加上 n−1 圈的环长,恰好等于从链表头部到入环点的距离。

因此,当发现 slow 与 fast 相遇时,我们再额外使用一个指针 ptr。起始,它指向链表头部;随后,它和 slow

每次向后移动一个位置。最终,它们会在入环点相遇。

876. 单链表的中点 | 力扣 | LeetCode | (easy)

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

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

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

快慢指针(慢走一步,快走两步),快指针到末尾,慢指针到中间,考虑到两个中间节点的话,偶数个结点。

因为快指针和快指针的下一个节点不为空,所以仍然需要遍历一遍while循环,s指针往后移动到第二个中间节点。

cpp 复制代码
class Solution {
public:
    ListNode* middleNode(ListNode* head) {
        ListNode *s=head,*f=head;
        while(f&&f->next){
            s=s->next;
            f=f->next->next;
        }
        return s;
    }
};

160. 相交链表 | 力扣 | LeetCode | (easy)

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

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

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

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

自定义评测:

评测系统 的输入如下(你设计的程序 不适用 此输入):

intersectVal - 相交的起始节点的值。如果不存在相交节点,这一值为 0 listA - 第一个链表 listB - 第二个链表

skipA - 在 listA 中(从头节点开始)跳到交叉节点的节点数 skipB - 在 listB

中(从头节点开始)跳到交叉节点的节点数 评测系统将根据这些输入创建链式数据结构,并将两个头节点 headA 和 headB

传递给你的程序。如果程序能够正确返回相交节点,那么你的解决方案将被 视作正确答案 。

示例 1:

输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,6,1,8,4,5], skipA

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

个节点;在 B 中,相交节点前有 3 个节点。 --- 请注意相交节点的值不为 1,因为在链表 A 和链表 B 之中值为 1 的节点 (A

中第二个节点和 B 中第三个节点) 是不同的节点。换句话说,它们在内存中指向两个不同的位置,而链表 A 和链表 B 中值为 8 的节点 (A

中第三个节点,B 中第四个节点) 在内存中指向相同的位置。

尝试失败,看灵神题解后只能说NB:

具体算法如下:

初始化两个指针 p=headA, q=headB。

不断循环,直到 p=q。

每次循环,p 和 q 各向后走一步。具体来说,如果 p 不是空节点,那么更新 p 为 p.next,否则更新 p 为 headB;如果 q 不是空节点,那么更新 q 为 q.next,否则更新 q 为 headA。

循环结束时,如果两条链表相交,那么此时 p 和 q 都在相交的起始节点处,返回 p;如果两条链表不相交,那么 p 和 q 都走到空节点,所以也可以返回 p,即空节点。

cpp 复制代码
class Solution {
public:
    ListNode* getIntersectionNode(ListNode* headA, ListNode* headB) {
        ListNode *a = headA, *b = headB;
        while (a != b) {
            a = a ? a->next : headB;
            b = b ? b->next : headA;
        }
        return a;
    }
};

19. 删除链表的倒数第N个结点 | 力扣 | LeetCode | (medium)

给你一个链表,删除链表的倒数第 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]

快慢指针解决

想象有一把长度固定的尺子,左端点在链表头部,右端点在正数第 n 个节点。向右移动尺子,当尺子右端点到达链表末尾时,左端点就在倒数第 n

个节点。

cpp 复制代码
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        //虚拟头结点
        ListNode dummy(0,head);
        ListNode *s=&dummy,*f=&dummy,*del;
        while(n--){
            //快指针先走n步
            f=f->next;
        }
        while(f->next){
            s=s->next;
            f=f->next;
        }
        del=s->next;
        s->next=s->next->next;
        delete del;
        return dummy.next;
    }
};
相关推荐
louisdlee.3 小时前
扫描线1:朴素扫描线
数据结构·c++·算法·扫描线
wan5555cn4 小时前
中国启用WPS格式进行国际交流:政策分析与影响评估
数据库·人工智能·笔记·深度学习·算法·wps
AndrewHZ4 小时前
【图像处理基石】图像形态学处理:从基础运算到工业级应用实践
图像处理·python·opencv·算法·计算机视觉·cv·形态学处理
仰泳的熊猫4 小时前
LeetCode:1905. 统计子岛屿
数据结构·c++·算法·leetcode
Lear4 小时前
【链表】LeetCode 206.反转链表
算法
Lear4 小时前
【链表】LeetCode 24.两两交换链表中的节点
算法
xiaoxiangwendao4 小时前
[26] 删除排序数组中的重复项
算法
THGML4 小时前
排序算法解析
数据结构·算法·排序算法
月夜的风吹雨4 小时前
【C++ string 类实战指南】:从接口用法到 OJ 解题的全方位解析
c++·接口·string·范围for·auto·力扣oj