当前位置: 首页 > news >正文

算法<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 的大小,把较小的节点接到结果链表上
请添加图片描述

迭代

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

递归

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. 分隔链表

尝试一下

class Solution {
public:ListNode* partition(ListNode* head, int x) {//初始化两个虚拟头结点分别代表大于val和小于valListNode 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;}
};

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

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

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


✅ 一句话总结:

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

📘 举个例子:

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

    node.val;      // ✅ 用 . 访问成员
    node.next;     // ✅ 用 . 访问成员
    
  • 如果是指针:

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

💡 原理解释:

p->val 等价于 (*p).val
也就是说:

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


⚠️ 在你的代码里:

head 是一个指针(ListNode*),
所以必须用:

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 个升序链表。

合并两个链表的扩展

尝试使用递归:

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 在链表中转圈了,说明链表中含有环。

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 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。

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

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

双(快慢)指针优化后:

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指针往后移动到第二个中间节点。

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,即空节点。

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

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;}
};
http://www.dtcms.com/a/535909.html

相关文章:

  • Linux小课堂: SELinux安全子系统原理与Apache网站目录访问问题解决方案
  • 云计算学习(三)——子网划分
  • 回森统一客服服务 AI+数字技术引领自智网络迈入新阶段
  • 云计算概念及虚拟化
  • 域名信息查询网站广告设计总结
  • qq网站登录入口蒙古文政务网站建设工作汇报
  • Spring Boot3零基础教程,Kafka 的简介和使用,笔记76
  • Rust Web实战:构建高性能并发工具的艺术
  • Kafka 全方位技术文档
  • (场景题)Java 导出 Excel 的两种方式
  • Nacos配置中心动态刷新全解析:从基础配置到源码级调优(二)
  • Excel小技巧:Excel数据带有单位应该如何运算求和?
  • 相机外参初始估计
  • Excel 学习笔记
  • 网站地图模板一站式网络营销
  • 如何检查开源CMS的数据库连接问题?
  • VTK入门:vtkQuadraticHexahedron——会“弯曲”的高精度六面体
  • 基于python大数据的城市扬尘数宇化监控系统的设计与开发
  • MCU定点计算深度解析:原理、技巧与实现
  • 【普中Hi3861开发攻略--基于鸿蒙OS】-- 第 28 章 WIFI 实验-UDP 通信
  • 【C++ string 类实战指南】:从接口用法到 OJ 解题的全方位解析
  • 门户网站 建设 如何写公司名称变更网上核名怎么弄
  • 并发编程基础
  • 第六部分:VTK进阶(第174章 空间流式与增量处理)
  • 智谱GLM-4.6/4.5深度解析:ARC三位一体的技术革命与国产模型崛起
  • 221. Java 函数式编程风格 - 从命令式风格到函数式风格:计算文件中包含指定单词的行数
  • Linux操作系统-进程的“夺舍”:程序替换如何清空内存、注入新魂?
  • 基于微信小程序的奶茶店点餐平台【2026最新】
  • 微信小程序-智慧社区项目开发完整技术文档(中)
  • 做设计用什么软件seo优化排名价格