【leetcode 力扣刷题】双指针//哈希表 解决链表有环等问题

news2024/11/20 11:27:00

双指针//哈希表 解决链表有环等问题

  • 19. 删除链表的倒数第N个结点
    • 遍历两次,先求得链表长度,再删除
    • 双指针,只遍历一次
  • 141. 环形链表
    • 哈希表
    • 快慢双指针
  • 142. 环形链表Ⅱ
    • 哈希表
    • 双指针
  • 面试题02.07. 链表相交
    • 哈希表
    • 双指针
      • 思路Ⅰ
      • 思路Ⅱ

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

题目链接:19. 删除链表的倒数第N个结点
题目内容:
在这里插入图片描述

如果把链表换成数组等数据结构,可以直接根据下标,从数组末端开始,找到倒数第N个元素。但是!链表的缺点就在于,查找元素的时候需要O(N)的时间复杂度。由于其结点都是通过前驱结点的next指针连接的(单向链表中),因此只能一个方向遍历链表结点,不能直接从后往前找到倒数第N个。

遍历两次,先求得链表长度,再删除

从前往后如何找到倒数第N个结点呢?假设链表长度为Size,那么倒数第N个结点,实际上就是正数第Size+1-N个结点。【比如Size=5,N=2,倒数第2个结点是正数第4个结点,Size-1+N=4】。由此可以想到最直接的办法:遍历两次链表,第一遍求得Size,第二遍定位到要删除的结点,并删除。 实际上删除结点,需要将待删除结点的前驱结点和后驱结点连接起来,因此找到前驱结点就行。 从head开始,找到第Size-N个结点,即为待删除结点的前驱节点。
代码实现(C++):

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        int Size = 0;
        //构建一个虚拟头节点,不需要单独讨论删除头节点的情况
        ListNode *dummyhead = new ListNode(0, head);
        ListNode *currNode = dummyhead->next;
        //统计链表长度【除虚拟头节点外的结点数】
        while(currNode){
            Size++;
            currNode = currNode->next;
        }
        currNode = dummyhead;
        //定位到要删除结点的前驱结点 第Size-N个结点
        for(int i = 0; i < Size - n; i++){
            currNode = currNode->next;
        }
        //要删除的结点是currNode->next
        ListNode *tmp = currNode->next;
        //将待删除结点tmp的前驱结点和后驱结点连接起来
        currNode->next = tmp->next;
        delete tmp;
        return dummyhead->next;
    }
};

双指针,只遍历一次

解决这道题目的重点是,直到倒数第N个结点,实际上就是整数的第Size+1-N个结点。问题在于是否需要求得Size呢? 如果不求Size怎么能够找到第Size+1-N个结点呢?
使用双指针,一个slow,一个fast。先让fast向前定位到第n个结点;之后slow从第一个结点开始,fast从第n个结点开始,slow和fast都每次向前移动一个结点,直到fast->next == null【即移动到链表最后一个结点,相当于定位到了第Size个结点,fast从第n个结点走到第Size个结点,走了Size-n步;slow从第1个结点,就走到了Size-n+1个结点】,slow对应的结点就是要删除的结点
在实际代码中,增加一个虚拟头结点,这样删除头结点的情况就不需要单独处理。slow从虚拟头结点开始,移动Size-n次,相当于移动到了被删除结点的前面一个结点【如果是一定要移动到被删除结点,那么终止条件改成fast==null即可,这样相当于fast从第n个结点移动到Size+1的位置,走了Size+1-n次,slow刚好走到Size+1-n结点的位置】。下图讨论了没有虚拟头节点和有虚拟头节点;slow最终指向待删除结点和待删除结点前面一个结点的情况:
在这里插入图片描述

以下代码(C++)实现的是上图的第二种情况,即有附加头节点,slow指向的待删除结点的前驱节点:

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode *dummyhead = new ListNode(0, head); //新建虚拟头节点
        ListNode *slow = dummyhead, *fast = dummyhead;
        //fast先指向第n个结点
        while(n){
            fast = fast->next;
            n--;
        }
        //fast指向最后一个结点结束
        while(fast->next){
            fast = fast->next;
            slow = slow->next;
        }
        //fast指针没用了,用来保存待删除的结点
        fast = slow->next;
        //建立新的连接
        slow->next = fast->next;
        delete fast; //删除结点
        return dummyhead->next;
    }
};

141. 环形链表

题目链接:环形链表
题目内容:
在这里插入图片描述
根据题意,实际上就是判断链表中是否存在环

哈希表

遍历链表,并将各个结点地址存入一个unordered_set中,如果某个地址在set中出现过,即可认为有环。

class Solution {
public:
    bool hasCycle(ListNode *head) {
        if(head == nullptr || head->next == nullptr)
            return false;
        unordered_set <ListNode *> visited; //用来存访问过的结点地址
        ListNode *currNode = head;  //从head结点开始遍历
        while(currNode){
            if(visited.count(currNode)) //如果当前结点地址已经访问过了,即有环
                return true;            
            visited.insert(currNode); //否则添加到set中
            currNode = currNode->next;//结点后移
        }
        return false;
    }
};

快慢双指针

经典的Floyd判圈算法。一个slow指针,一个fast指针。slow从头节点开始每次向前移动一个结点,fast从头节点开始每次向前移动两个结点:

  • 如果没有环,fast因为移动更快,而先遍历完链表;如果fast==null || fast->next == null即可认为没有环;
  • 如果有环,那么fast只是比slow更先进入环内绕圈,待slow也进入环内,fast每次在圈内走两步,slow走一步,fast和slow之间的距离就减少一步,最后fast最追上slow,即出现fast == slow,即可判断有环。

代码如下(C++):

class Solution {
public:
    bool hasCycle(ListNode *head) {
        if(head == nullptr) 
            return false;       
        ListNode *slow = head, *fast = head;
        while(fast != nullptr && fast->next !=nullptr ){
            slow = slow->next;
            fast = fast->next->next;            
            if(slow == fast) //fast追上了slow,有环
                return true;
        }
       //退出循环是因为fast先遍历完链表,无环
       return false;
    }
};

142. 环形链表Ⅱ

题目链接:142. 环形链表Ⅱ
题目内容:
在这里插入图片描述
理解题意:实际上就是在判断链表是否有环的基础上,找出进入环的第一个结点
在这里插入图片描述

哈希表

同样使用哈希表,和141题的判断是否有环一样,用set存各个结点的地址,如果出现了重复的地址,且第一次重复的就是环的入口

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        if(head == nullptr || head->next == nullptr)
            return nullptr;
        unordered_set <ListNode *> visited;
        ListNode *currNode = head;
        while(currNode){
            if(visited.count(currNode))
                return currNode; //第一次重复的地址,就是环的入口
            
            visited.insert(currNode);
            currNode = currNode->next;
        }
        return nullptr;
    }
};

双指针

这里涉及到一些数学推理了……依旧是slow、fast两个指针,从头节点开始请看下图:
在这里插入图片描述
这里的x、y、z表示某一段链表中的节点数,都是左开右闭的,即起始结点不包括,终止结点包括【这个很重要的】。x:从头节点开始,到环的起始结点的结点数量;y:slow结点进入环以后,即从入环节点开始,移动y个节点,slow和fast相遇;z:从slow和fast相遇节点开始,slow(或fast)要移动z个节点,到达入环节点。
接下来分析x、y、z之间的数学关系,slow和fast相遇时:

  • fast先进入环,开始绕圈【假设绕了n圈才和slow相遇,n≥1】,所以fast已经遍历的节点数:x+(y+z)*n+y
  • slow后进入环,入环后第一圈移动y个节点就和fast相遇了,slow遍历的节点数:x+y;【这里肯定是第一圈没走完就能和fast相遇的,因为slow入环的时候fast在环中一个位置,slow在入环节点处,假设fast距离入环节点m个节点的话,也就是和slow相差m个节点的距离,之后每一次移动,fast向slow靠近一个节点,那么移动m次就相遇,即题目中的y。m肯定是小于环的节点数的。】

因为fast每次向前移动两个,slow每次移动一个,所以:2*(x+y) = x+n*(y+z)+y;等式处理一下就得到了x = (n-1)*(y+z) +z,如果n=1,x=z,即index1指针从head开始,index2指针从slow与fast相遇的节点开始,两个分别向前移动,移动相同次数,index1和index2会相遇,相遇处即为入环处;如果n>1,就是index2在环内绕了(n-1)圈后和index1在入环处相遇。总之就是index1和index2会相遇,相遇处就是入环处。代码如下(C++):

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        if(head == nullptr || head->next == nullptr)
            return nullptr;
        ListNode *slow = head, *fast = head;//fast和slow都从头节点开始
        do{
            if(fast && fast->next){
                slow = slow->next;
                fast = fast->next->next;
            }
            else
                return nullptr; //如果fast先遍历完链表,即没有环
        }while(slow != fast); //跳出循环即为有环
        slow = head; 
        //slow从head开始,fast从相遇点开始
        //二者每次向前移动一个节点,相遇处即为入环处
        while(slow != fast){
            slow = slow->next;
            fast = fast->next;
        }
        return slow;        
    }
};

面试题02.07. 链表相交

题目链接:面试题02.07. 链表相交
题目内容:在这里插入图片描述
理解题意:判断两个链表有没有相交的部分

哈希表

如果两个链表相交,那么从相交节点开始,之后的节点都是公共的,是相同的地址。所以还是可以用哈希表解决,先遍历其中一个链表,然后将其每个节点的地址存入set中;之后遍历第二个链表,并判断每个节点地址是否在set中,如果在,那么直接返回,这个地址就是相交起始节点。如果遍历完第二个链表都没有公共的节点地址,那么就是不相交的。代码如下(C++):

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        unordered_set <ListNode *> visited;
        ListNode *currNode = headA;
        while(currNode){ //遍历第一个链表并将各个节点的地址存在set中
            visited.insert(currNode);
            currNode = currNode->next;
        }        
        currNode = headB;
        while(currNode){//遍历第二个链表并判断每个节点地址是否出现在set中
            if(visited.count(currNode))
                return currNode; //找到相交起始点
            currNode = currNode->next;
        }
        return nullptr;
    }
};

双指针

按道理,遍历两个链表的同时,对比是否有相同的节点地址,就能直到是否有交叉部分。但是问题在意,两个链表长度不一样,如果都从头开始遍历的话,即便两个链表是相交的,但是由于对比的节点没有对齐,而结果不正确。 所以解决办法是怎么把两个链表对齐

思路Ⅰ

两个链表相交,从相交点开始到末尾节点的,都是重合的。在相交节点之前,链表A有几个节点、链表B有几个节点都不重要,也不确定。所以就是把两个链表按尾节点对齐。先遍历链表A和B统计两个链表的节点数【假设为Size_A和Size_B】,节点数大的链表先移动max(Siez_A,Size_B) - min(Size_A,Size_B)个节点,与节点数小的链表的头节点对齐【也相当于是为按尾节点对齐】,之后逐个对比两个链表的结点的地址。
在这里插入图片描述
代码如下(C++):

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
    	//统计两个链表中的节点数
        int len_A = 0, len_B = 0;
        ListNode *currNodeA = headA, *currNodeB = headB;
        while(currNodeA){ //统计链表A
            len_A++;
            currNodeA = currNodeA->next;
        }
        while(currNodeB){//统计链表B
            len_B++;
            currNodeB = currNodeB->next;
        }
        //从新从头开始遍历两个链表
        currNodeA = headA;
        currNodeB = headB;
        while(len_A > len_B){ //如果Len_A更大进入这个循环
            currNodeA = currNodeA->next;
            len_A--;
        }
        while(len_B > len_A){//如果len_B更大进入这个循环
            currNodeB = currNodeB->next;
            len_B--;
        }
        //两个链表已经对齐了,开始逐个结点对比
        while(currNodeA && currNodeB){
            if(currNodeA == currNodeB)
                return currNodeA;
            currNodeA = currNodeA->next;
            currNodeB = currNodeB->next;
        }
        return nullptr;
    }
};

思路Ⅱ

虽然两个链表长度不一样,不能对齐,那么把链表A和链表B拼起来呢?即一个指针currNodeA先遍历链表A,遍历完链表A后从头开始遍历链表B;另一个指针currNodeB先遍历链表B,遍历完链表B后从头开始遍历链表A。 那么currNodeA和currNodeB都遍历两个链表,如果链表A和链表B有相交部分,currNodeA和currNodeB就会相等且不为null;如果没有相交的部分,currNodeA和currNodeB最后就遍历完链表,均为null。
在这里插入图片描述
代码实现如下(C++):

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode *currNodeA = headA, *currNodeB = headB;
        while(currNodeA != currNodeB){
            if(currNodeA)
                currNodeA = currNodeA->next;
            else 
                currNodeA = headB;
            if(currNodeB)
                currNodeB = currNodeB->next;
            else
                currNodeB = headA;
        }
        return currNodeA;
    }
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/919107.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

arm:day9

1。思维导图 2..I2C实验&#xff0c;检测温度和湿度 iic.h #ifndef __IIC_H__ #define __IIC_H__ #include "stm32mp1xx_gpio.h" #include "stm32mp1xx_rcc.h" #include "gpio.h" /* 通过程序模拟实现I2C总线的时序和协议* GPIOF ---> AHB4…

基于jenkins构建生成CICD环境

目录 一、安装配置jenkins 1、环境配置 2、软件要求 3、jdk安装&#xff08;我是最小化安装&#xff0c;UI自带java要先删除rm -rf /usr/local/java 4、安装jenkins-2.419-1.1 二、Jenkins配置 1、修改jenkins初始密码 2、安装 Jenkins 必要插件 3、安装 Publish Over SS…

RabbitMQ笔记-RabbitMQ基本术语

RabbitMQ基本术语 相关概念; 生产者&#xff08;Producer&#xff09;&#xff1a;投递消息。消息&#xff1a;消息体&#xff08;payload&#xff09;标签&#xff08;label&#xff09;&#xff1b;生产者把消息交给rabbitmq&#xff0c;rabbitmq会根据标签把消息发给感兴趣…

深入理解回调函数qsort:从入门到模拟实现

&#x1f341;博客主页&#xff1a;江池俊的博客 &#x1f4ab;收录专栏&#xff1a;C语言进阶之路 &#x1f4a1;代码仓库&#xff1a;江池俊的代码仓库 &#x1f3aa;我的社区&#xff1a;GeekHub &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐ 文章目录 前…

layui框架学习(38:数据表格_自定义单元格样式)

layui中的数据表格模块table中的列参数中的基础参数templet支持通过基于laytpl语法的自定义列模板处理或展示单元格数据。本文首先学习layui示例中的表格样式设置示例&#xff08;参考文献3&#xff09;&#xff0c;然后基于之前的传感器检测数据的表格示例&#xff0c;测试基于…

SHELL 基础 入门(三) Bash 快捷键 命令执行顺序,详解通配符

目录 Bash 常用快捷键 输入输出重定向 << 用法 输出重定向 命令执行顺序 ; 分号 && || 通配符 传统通配符 &#xff1f; * [ ] [ - ] [ ^ ] 常用字符 强调 &#xff1a; { } 生成序列 Bash 常用快捷键 Ctrl A 把光…

xargs 的用法 在1个文件夹中批量删除文件,这些删除的文件名是另一个文件夹中的文件名。

xargs 的用法 在1个文件夹中批量删除文件&#xff0c;这些删除的文件名是另一个文件夹中的文件名。 1、问题背景 应用场景 1、问题背景 应用场景 在二进制部署docker时&#xff0c;会把docker的所有可执行文件复制到/usr/bin下。 如果说复制过去后&#xff0c;想要反悔&#x…

使用proxman对iOS真机进行抓包

1 打开手机的safari 输入地址 http://proxy.man/ssl 2 下载证书代开设置页面&#xff0c;安装证书 设置信任证书 打开手机设置 &#xff0c;点击通用 点击关于本机、 点击证书信任设置 打开信任设置开关 4 设置手机代理 查看需要设置的代理地址 打开界面 在手机中按…

IDEA常用插件之私有注解Private Notes

文章目录 IDEA常用插件之私有注解Private Notes功能使用方法下载插件设置快捷键添加注释注释数据保存目录其他设置参数 更换电脑注释迁移同步 IDEA常用插件之私有注解Private Notes 功能 添加私有注解提交git后其他人看不到给源码添加注解 使用方法 下载插件 进入插件页面…

数据结构入门 — 顺序表详解

前言 数据结构入门 — 顺序表详解 博客主页链接&#xff1a;https://blog.csdn.net/m0_74014525 关注博主&#xff0c;后期持续更新系列文章 文章末尾有源码 *****感谢观看&#xff0c;希望对你有所帮助***** 文章目录 前言一、顺序表1. 顺序表是什么2. 优缺点 二、概念及结构…

Win解答 | 解决键盘中 字母+空格 导致的输入法弹窗导致的一系列问题

近三个月来&#xff0c;一直都有一个键盘组合键的问题影响我的电脑使用&#xff0c;不管是打字还是打游戏&#xff0c;都会出现按键盘的 字母空格 弹出一个特殊符号的候选框&#xff0c;如下图所示 图片中为 S空格 所出现的弹窗 一个看似方便&#xff0c;实则难受的功能 其实打…

【AndroidStudio】java.nio.charset.MalformedInputException: Input length = 1

java.nio.charset.MalformedInputException: Input length 1 可以参考这个文章处理下编码格式&#xff1a;https://blog.csdn.net/twotwo22222/article/details/124605029java.nio.charset.MalformedInputException: Input length 1是因为你的配置文件里面有中文或者是你的编…

APEX内置验证与授权管理

参考博客&#xff1a;&#xff08;真的很好的教程&#xff0c;感谢&#xff01;&#xff09; 09技术太卷我学APEX-定制页面及导航菜单权限_白龙马5217的博客-CSDN博客https://blog.csdn.net/html5builder/article/details/128816236?spm1001.2014.3001.5501 1 应用程序安全性…

Git 安装、配置并把项目托管到码云 Gitee

错误聚集篇&#xff1a; 由于我 git 碰见大量错误&#xff0c;所以集合了一下&#xff1a; git 把项目托管到 码云出现的错误集合_打不着的大喇叭的博客-CSDN博客https://blog.csdn.net/weixin_49931650/article/details/132460492 1、安装 git 1.1 安装步骤 1.1.1 下载对应…

Linux系统编程:进程信号的处理

目录 一. 用户态和内核态 1.1 用户态和内核态的概念 1.2 用户态和内核态之间的切换 二. 信号的捕捉和处理 2.1 捕捉信号的时机 2.2 多次向进程发送同一信号 2.3 sigaction 函数 三. 可重入函数和不可重入函数 四. volatile 关键字 五. SIGCHLD信号 5.1 SIGCHLD信号的…

在互联网+的背景下,企业如何创新客户服务?

随着互联网的发展&#xff0c;开始数字化转型的潮流&#xff0c;移动互联网平台为各个行业带来了发展的新方向。企业有了移动互联网的加持&#xff0c;为客户提供了更好的服务。当移动互联网平台能够为客户提供更好的用户体验时&#xff0c;相应地&#xff0c;客户也给企业带来…

OpenCV项目开发实战--基于Python/C++实现鼠标注释图像和轨迹栏来控制图像大小

鼠标指针是图形用户界面 (GUI) 中的关键组件。没有它,您就无法真正考虑与 GUI 进行交互。那么,让我们深入了解 OpenCV 中鼠标和轨迹栏的内置函数。我们将演示如何使用鼠标来注释图像,以及如何使用轨迹栏来控制图像的大小 我们将使用下图来演示 OpenCV 中鼠标指针和轨迹栏功能…

Arcgis colorRmap

arcgis中colorRmap对应的名称&#xff1a; 信息来源&#xff1a;https://developers.arcgis.com/documentation/common-data-types/raster-function-objects.htm 点击该网页&#xff0c;并直接搜索“rasterFunction”&#xff0c;直接索引到该位置。 在arcpy中使用方法&#…

Linux安装jdk、mysql、并部署Springboot项目

&#x1f61c;作 者&#xff1a;是江迪呀✒️本文关键词&#xff1a;Linux、环境安装、JDK安装、MySQL、MySQL安装☀️每日 一言&#xff1a;知行合一&#xff01; 文章目录 一、前言二、安装步骤2.1 安装JDK&#xff08;1&#xff09;创建文件夹&#xff08;便于后…

2828. 判别首字母缩略词

2828. 判别首字母缩略词 C代码1&#xff1a; bool isAcronym(char ** words, int wordsSize, char * s){if (wordsSize ! strlen(s)) {return false;} for (int i 0; i < wordsSize; i) { // 遍历所有&#xff0c;没有不满足的就是满足的if (words[i][0] ! s[i]) {return…