数据结构:链表的双指针技巧
文章目录
- 一、链表相交问题
- 二、单链表判环问题
- 三、回文链表
- 四、重排链表结点
初学双指针的同学,请先弄懂删除链表的倒数第 N 个结点。
并且在学习这一节时,不要将思维固化,认为只能这样做,这里的做法只是技巧。
一、链表相交问题
LeetCode:160.相交链表
题目要求时间复杂度为O(L1+L2),空间复杂度为O(1),实际上并不是说只能遍历一次,单个链表遍历常数次,时间复杂度仍为O(L)。我们可以采用如下方法解决:
-
计算两个链表的长度:首先遍历两个链表,计算它们各自的长度(
lenA和lenB),同时检查链表的末尾节点是否相同。如果末尾节点不同,则两个链表不相交,直接返回NULL。这一步也确保了,如果两个链表相交,它们的末尾节点必定是相同的。(对于存在相交的链表,它们的的末尾结点一定是一样的!) -
调整起点:为了同步遍历两个链表找到交点,需要从两个链表相同的“距离”开始遍历。由于两个链表可能长度不同,我们先计算长度差
abs(lenA-lenB)。然后让较长的链表的指针先前进这个长度差的步数。这样做的目的是让两个链表的指针能够在同一起跑线上“开始比赛”。 -
同步前进直到找到交点:之后,同时前进两个链表的指针,直到它们相遇。由于步骤2已经确保了两个指针距离链表末尾的距离相同,所以它们会在交点相遇。如果两个链表有交点,那么这个交点是两个指针第一次相遇的地方。
这段代码的关键在于通过计算长度差并同步前进两个指针来找到可能的交点。这种方法不需要修改链表结构,也不需要额外的存储空间,效率较高。在最坏的情况下,时间复杂度是O(L1+L2),其中L1和L2分别是两个链表的长度。
class Solution {
public:ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {//if(!headA||!headB) return NULL;int lenA=0,lenB=0;ListNode * travelA=headA,* travelB=headB;while(travelA->next||travelB->next){if(travelA->next) {travelA=travelA->next;++lenA;}if(travelB->next) {travelB=travelB->next;++lenB;}}if(travelA!=travelB) return NULL;if(lenB>lenA) swap(headA,headB);lenA=abs(lenA-lenB);//复用for(int i=0;i<lenA;++i) headA=headA->next;while(headA!=headB){headA=headA->next;headB=headB->next;}return headA;}
};
防止大脑已经不会暴力,暴力解法:
①对于L1,从头遍历L1的每一个结点,判断该结点是否在L2中,如果在,则是相交结点。(从头遍历,第一个在的是相交结点),如果每次都遍历一次L2,则时间复杂度O(L1*L2)
②无脑的哈希优化,将L2的结点存入一个哈希表(集合),每次判断时只平均需要O(1)的时间,所以时间复杂度为O(L1+L2),空间复杂度O(L2)。
哈希优化:
class Solution {
public:ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {//if(!headA||!headB) return NULL;unordered_set<ListNode * > Set;while(headA){Set.insert(headA);headA=headA->next;}while(headB){if(Set.count(headB)) return headB;headB=headB->next;}return NULL;}
};
二、单链表判环问题
LeetCode:141.环型链表
题目要求使用空间复杂度为O(1),因此不能用哈希解决,用哈希只需要遍历的过程中将结点存入哈希表,每次遍历先判断该结点是否在哈希表中,如果不在则存入哈希表,如果在则找到环。但是哈希表的空间复杂度为O(L),因此我们只能用其他方法。
这里使用双指针,一个慢指针slow,一个快指针fast,slow一次走一步,fast一次走两步。类似于跑步比赛,从同一个长道出发,跑向运动场的跑道上跑步,由于快指针跑的速度是慢指针的两倍,快指针总会多跑一圈然后超过慢指针。
因此如果只需要判断是否存在环只需要这样做,无环的话fast一定先跑到底:
class Solution {
public:bool hasCycle(ListNode *head) {if(!head||!head->next) return false;ListNode * slow=head;ListNode * fast=head;while(fast!=nullptr && fast->next!=nullptr){slow=slow->next;fast=fast->next->next;if(fast==slow) return true;}return false;}
};
不过,我们知道在行走时,还有信息我们没有用到,我们是否能利用这些信息找到环的入口呢?信息:
- 慢指针走的步数:
step_slow,快指针走的步数:step_fast,则有step_fast=step_slow*2。 - 假设环外结点数为
a,环中结点数为b,设x是fast和slow相遇时离换入口的距离,则step_fast=a+nb+x,step_slow=a+cb+x,(0<=x<b)。 - 联立可得①a+nb+x=2a+2x+2cb②nb=a+x+2cb③(n-2c)b=a+x。
- 由③可知
a+x>0,且a+x是圈中结点数的倍数,换句话说,由于现在走到的位置是x,则在fast和slow相遇的位置,再走a步可以走到环的入口(因为再走这么多步刚好是环的倍数步),换句话说,如果现在有一个指针p从环外起点出发,每次走一步,与fast或slow一同行走,走完环外结点个数步(a步)之后会在环的入口处相遇!
寻找环的入口:
ListNode *detectCycle(ListNode *head) {ListNode *slow = head, *fast = head;// 第一次遍历,找到快慢指针相遇点while (fast != nullptr && fast->next != nullptr) {slow = slow->next;fast = fast->next->next;if (slow == fast) { // 发现环// 将其中一个指针(这里选择slow)移回头节点slow = head;// 两个指针以相同速度移动,再次相遇点即为环入口while (slow != fast) {slow = slow->next;fast = fast->next;}return slow; // 返回环入口}}return nullptr; // 无环
}
三、回文链表
LeetCode:234.回文链表
回文链表,我们可以这样做,我们单独存一个原链表的反置之后的链表,然后反置的 和 原链表 从首位置开始齐步向前就行。(当然逆序的话,使用栈也是可以的!栈就相当于你存进去的东西想逆着拿出来,换句话说,你只需要逆序来查看某个东西的元素,存入栈是很好的选择!)不过时间复杂度需要的是O(1),我们需要一点点技巧。但是单链表怎么也不可能反着遍历呀!怎么办呢?!

将中点之后的链表部分翻转吧朋友:找到链表中点,然后将后面的链表翻转,然后就可以首尾齐进了。翻转链表只需要O(1)的时间复杂度,三个指针~,不过如果原题说不允许修改原结构,那就再翻转过了即可(虽然输入和输出只是一个接口,但是这确实只能说很离谱)。
反转链表的后半部分:
- 找到中点:使用快慢指针找到链表的中间节点。
- 反转后半部分:从中间节点开始反转链表的后半部分。
- 比较前后半部分:将前半部分和反转后的后半部分进行比较,如果每个节点的值都相同,则链表是回文。
- 恢复链表(可选):如果需要保持原链表的结构,可以再次反转后半部分以恢复原链表。
这种方法的空间复杂度为O(1),但需要改变链表结构(如果不恢复的话)。
class Solution {
public:bool isPalindrome(ListNode* head) {if (head == nullptr) {return true;}// 找到前半部分链表的尾节点并反转后半部分链表ListNode* firstHalfEnd = endOfFirstHalf(head);ListNode* secondHalfStart = reverseList(firstHalfEnd->next);// 判断是否回文ListNode* p1 = head;ListNode* p2 = secondHalfStart;bool result = true;while (result && p2 != nullptr) {if (p1->val != p2->val) {result = false;}p1 = p1->next;p2 = p2->next;} // 还原链表并返回结果firstHalfEnd->next = reverseList(secondHalfStart);return result;}ListNode* reverseList(ListNode* head) {ListNode* prev = nullptr;ListNode* curr = head;while (curr != nullptr) {ListNode* nextTemp = curr->next;curr->next = prev;prev = curr;curr = nextTemp;}return prev;}ListNode* endOfFirstHalf(ListNode* head) {ListNode* fast = head;ListNode* slow = head;while (fast->next != nullptr && fast->next->next != nullptr) {fast = fast->next->next;slow = slow->next;}return slow;}
};
四、重排链表结点

这个和回文链表是一样的,将中点之后的链表部分翻转,然后就可以一个一个选了。
相关文章:
数据结构:链表的双指针技巧
文章目录 一、链表相交问题二、单链表判环问题三、回文链表四、重排链表结点 初学双指针的同学,请先弄懂删除链表的倒数第 N 个结点。 并且在学习这一节时,不要将思维固化,认为只能这样做,这里的做法只是技巧。 一、链表相交问题 …...
用WHERE命令可以在命令行搜索文件
文章目录 用WHERE命令可以在命令行搜索文件概述笔记没用的小程序END 用WHERE命令可以在命令行搜索文件 概述 想确认PATH变量中是否存在某个指定的程序(具体是在PATH环境变量中给出的哪个路径底下?). 开始不知道windows有where这个命令, 还自己花了2个小时写了一个小程序. 后…...
持续交付/持续部署流水线介绍(CD)
目录 一、概述 二、典型操作流程 2.1 CI/CD典型操作流 2.2 CI/CD操作流程说明 2.3 总结 三、基于GitHubDocker的持续交付/持续部署流水线(公有云) 3.1 基于GitHubDocker的持续交付/持续部署操作流程示意图 3.2 GitHubDocker持续交付/持续部署流水…...
第四百三十八回
文章目录 1. 概念介绍2. 思路与方法2.1 实现思路2.2 实现方法 3. 示例代码4. 内容总结 们在上一章回中介绍了"不同平台上换行的问题"相关的内容,本章回中将介绍如何在页面上显示蒙板层.闲话休提,让我们一起Talk Flutter吧。 1. 概念介绍 我们…...
Python学习:面相对象
面向对象 面向对象技术简介 类(Class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。方法:类中定义的函数。类变量:类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实…...
SSM学习——Spring AOP与AspectJ
Spring AOP与AspectJ 概念 AOP的全称为Aspect-Oriented Programming,即面向切面编程。 想象你是汉堡店的厨师,每一份汉堡都有好几层,这每一层都可以视作一个切面。现在有一位顾客想要品尝到不同风味肉馅的汉堡,如果按照传统的方…...
Android 使用LeakCanary检测内存泄漏,分析原因
内存泄漏是指无用对象(不再使用的对象)持续占有内存或无用对象的内存得不到及时释放,从而造成内存空间的浪费称为内存泄漏。 平时我们在使用app时,少量的内存泄漏我们是发现不了的,但是当内存泄漏达到一定数量时&…...
Linux部署Kafka2.8.1
安装Jdk 首先确保你的机器上安装了Jdk,Kafka需要Java运行环境,低版本的Kafka还需要Zookeeper,我此次要安装的Kafka版本为2.8.1,已经内置了一个Zookeeper环境,所以我们可以不部署Zookeeper直接使用。 1、解压Jdk包 t…...
【pytest、playwright】allure报告生成视频和图片
目录 1、修改插件pytest_playwright 2、conftest.py配置 3、修改pytest.ini文件 4、运行case 5、注意事项 1、修改插件pytest_playwright pytest_playwright.py内容如下: # Copyright (c) Microsoft Corporation. # # Licensed under the Apache License, Ver…...
浅谈iOS开发中的自动引用计数ARC
1.ARC是什么 我们知道,在C语言中,创建对象时必须手动分配和释放适量的内存。然而,在 Swift 中,当不再需要类实例时,ARC 会自动释放这些实例的内存。 Swift 使用 ARC 来跟踪和管理应用程序的内存,其主要是由…...
Spring IoCDI(2)
IoC详解 通过上面的案例, 我们已经知道了IoC和DI的基本操作, 接下来我们来系统地学习Spring IoC和DI的操作. 前面我们提到的IoC控制反转, 就是将对象的控制权交给Spring的IoC容器, 由IoC容器创建及管理对象. (也就是Bean的存储). Bean的存储 我们之前只讲到了Component注解…...
30. UE5 RPG GamplayAbility的配置项
在上一篇文章,我们介绍了如何将GA应用到角色身上的,接下来这篇文章,将主要介绍一下GA的相关配置项。 在这之前,再多一嘴,你要能激活技能,首先要先应用到ASC上面,才能够被激活。 标签 之前介绍…...
提升自己最快的方式是什么?
提升自己最快的方式通常涉及到个人成长的各个方面,包括心理、情感、技能和知识等。根据查阅到的资料,以下是一些具体的方法和步骤,帮助你快速提升自己: 1. 培养屏蔽力 荷兰畅销书作家罗伊马丁纳提到,屏蔽力是个人成长…...
题目:一个5位数,判断它是不是回文数。即12321是回文数,个位与万位相同,十位与千位相同。
题目:一个5位数,判断它是不是回文数。即12321是回文数,个位与万位相同,十位与千位相同。 There is no nutrition in the blog content. After reading it, you will not only suffer from malnutrition, but also impotence…...
《HelloGitHub》第 96 期
兴趣是最好的老师,HelloGitHub 让你对编程感兴趣! 简介 HelloGitHub 分享 GitHub 上有趣、入门级的开源项目。 https://github.com/521xueweihan/HelloGitHub 这里有实战项目、入门教程、黑科技、开源书籍、大厂开源项目等,涵盖多种编程语言 …...
C++tuple类型
tuple 类型 tuple是类似pair的模板。 每个pair的成员类型都不相同,但每个pair都恰好有两个成员。不同tuple类型的成员类型也不相同,但一个tuple可以有任意数量的成员。 每个确定的tuple类型的成员数目是固定的,但一个tuple类型的成员数目可…...
亚远景科技-浅谈ASPICE标准和ASPICE认证/评估
ASPICE(Automotive SPICE)是一种针对汽车行业的软件开发过程的评估模型,它旨在帮助汽车制造商和供应商提高软件开发过程的能力和质量,从而提升产品的质量、安全性和效率。 ASPICE标准涵盖了软件开发的各个阶段和活动,…...
PHP性能提升方案
一、背景与介绍 PHP语言开发效率高,特别应用于适合中小型项目,对于创业初期敏捷开发验证项目可行性或者Demo演示绝对占据优势。 但是随着现在Web应用的复杂性,针对项目要适应高并发、高流量的访问特性,PHP确实在性能方面相对Go、J…...
关系(二)利用python绘制热图
关系(二)利用python绘制热图 热图 (Heatmap)简介 热图适用于显示多个变量之间的差异,通过颜色判断彼此之间是否存在相关性。 快速绘制 基于seaborn import seaborn as sns import pandas as pd import numpy as np i…...
P8597 [蓝桥杯 2013 省 B] 翻硬币
# [蓝桥杯 2013 省 B] 翻硬币 ## 题目背景 小明正在玩一个“翻硬币”的游戏。 ## 题目描述 桌上放着排成一排的若干硬币。我们用 * 表示正面,用 o 表示反面(是小写字母,不是零),比如可能情形是 **oo***oooo&#x…...
设计模式和设计原则回顾
设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...
java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别
UnsatisfiedLinkError 在对接硬件设备中,我们会遇到使用 java 调用 dll文件 的情况,此时大概率出现UnsatisfiedLinkError链接错误,原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用,结果 dll 未实现 JNI 协…...
el-switch文字内置
el-switch文字内置 效果 vue <div style"color:#ffffff;font-size:14px;float:left;margin-bottom:5px;margin-right:5px;">自动加载</div> <el-switch v-model"value" active-color"#3E99FB" inactive-color"#DCDFE6"…...
学习STC51单片机31(芯片为STC89C52RCRC)OLED显示屏1
每日一言 生活的美好,总是藏在那些你咬牙坚持的日子里。 硬件:OLED 以后要用到OLED的时候找到这个文件 OLED的设备地址 SSD1306"SSD" 是品牌缩写,"1306" 是产品编号。 驱动 OLED 屏幕的 IIC 总线数据传输格式 示意图 …...
【AI学习】三、AI算法中的向量
在人工智能(AI)算法中,向量(Vector)是一种将现实世界中的数据(如图像、文本、音频等)转化为计算机可处理的数值型特征表示的工具。它是连接人类认知(如语义、视觉特征)与…...
LLM基础1_语言模型如何处理文本
基于GitHub项目:https://github.com/datawhalechina/llms-from-scratch-cn 工具介绍 tiktoken:OpenAI开发的专业"分词器" torch:Facebook开发的强力计算引擎,相当于超级计算器 理解词嵌入:给词语画"…...
k8s业务程序联调工具-KtConnect
概述 原理 工具作用是建立了一个从本地到集群的单向VPN,根据VPN原理,打通两个内网必然需要借助一个公共中继节点,ktconnect工具巧妙的利用k8s原生的portforward能力,简化了建立连接的过程,apiserver间接起到了中继节…...
JavaScript基础-API 和 Web API
在学习JavaScript的过程中,理解API(应用程序接口)和Web API的概念及其应用是非常重要的。这些工具极大地扩展了JavaScript的功能,使得开发者能够创建出功能丰富、交互性强的Web应用程序。本文将深入探讨JavaScript中的API与Web AP…...
【JVM面试篇】高频八股汇总——类加载和类加载器
目录 1. 讲一下类加载过程? 2. Java创建对象的过程? 3. 对象的生命周期? 4. 类加载器有哪些? 5. 双亲委派模型的作用(好处)? 6. 讲一下类的加载和双亲委派原则? 7. 双亲委派模…...
【Linux系统】Linux环境变量:系统配置的隐形指挥官
。# Linux系列 文章目录 前言一、环境变量的概念二、常见的环境变量三、环境变量特点及其相关指令3.1 环境变量的全局性3.2、环境变量的生命周期 四、环境变量的组织方式五、C语言对环境变量的操作5.1 设置环境变量:setenv5.2 删除环境变量:unsetenv5.3 遍历所有环境…...
