【算法练习Day4】 两两交换链表节点删除链表倒数第 N 个结点环形链表 II

news2024/11/24 3:04:05

在这里插入图片描述

​📝个人主页:@Sherry的成长之路
🏠学习社区:Sherry的成长之路(个人社区)
📖专栏链接:练题
🎯长路漫漫浩浩,万事皆有期待

文章目录

  • 两两交换链表中的节点
    • 一般思路
    • 递归思路
    • 其他问题
  • 删除链表的倒数第 N 个结点
    • 暴力求解
    • 双指针法
    • 其他问题
  • 环形链表 II
    • 公式推导
    • 相交链表
  • 总结:

两两交换链表中的节点

24. 两两交换链表中的节点- 力扣(LeetCode)
在这里插入图片描述

该题是交换一条链表中两两相连的节点,注意不能直接交换节点数值,而是要控制指针完成节点的交换,这道题我一听题目感觉好像是两个链表的节点互相交换的题目,但实际看到题发现并不是,看到题目有点懵,不知道该怎么交换。

这道题的要点就是要找好,交换节点哪些节点该用临时指针保存,而哪些节点并不需要保存,一定要理清思路,才不会在交换节点时候将自己绕进去。

一般思路

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        ListNode* dummyHead = new ListNode(0); // 设置一个虚拟头结点
        dummyHead->next = head; // 将虚拟头结点指向head,这样方面后面做删除操作
        ListNode* cur = dummyHead;
        while(cur->next != nullptr && cur->next->next != nullptr) {
            ListNode* tmp = cur->next; // 记录临时节点
            ListNode* tmp1 = cur->next->next->next; // 记录临时节点

            cur->next = cur->next->next;    // 步骤一
            cur->next->next = tmp;          // 步骤二
            cur->next->next->next = tmp1;   // 步骤三

            cur = cur->next->next; // cur移动两位,准备下一轮交换
        }
        return dummyHead->next;
    }
};

这道题也采用了虚拟头节点的技巧,交换数据时,需要找到要交换数据的上一个节点,例如题目测试用例1中,1,2节点交换信息,而1节点的上一个虚拟头节点连接的是2号节点,然后2号节点链接一号节点完成两两交换后,1节点连接之前2号节点后面的链表部分,如果没有虚拟头节点作为辅助链接,那么在第一次交换,也就是头节点需要进行两两交换时,就要单独判断一次,增加代码量的同时,也更容易出错。

上述的讲解,就已经明确了我们需要两个临时变量来指向节点,一个用来指向待交换的第一个节点,另一个用来指向第二个待交换的节点的next节点,以防交换完之后找不到后面的链表,而存储第一个待交换的节点的目的是,前一个节点链接第二个待交换的节点,后第二个节点再连第一个,方便交换。

递归思路

先两两交换后面的节点,再交换前面的节点,链接上后面交换好了的节点,注意递归结束是空节点或者只剩一个节点
在这里插入图片描述

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        if(head==nullptr||head->next==nullptr)
        {
            return head;
        }
        auto tmp=swapPairs(head->head->next);//先两两交换后面的节点,返回新的头节点
        auto ret=head->next;//先存一下最终返回的节点
        head->next->next=head;//交换前面的节点
        head->next=tmp;//链接上后面交换好了的节点
        return ret;//返回现在的头结点
    }
};

其他问题

● while (cur->next != nullptr && cur->next->next != nullptr) 这边为什么是&& 不是|| 一个是对于偶数个结点的判断 一个是奇数个结点 那不应该是||的关系吗?
奇数节点就不需要交换了,所以只有满足后面有偶数个节点的时候才会进入循环

● 循环条件,什么情况应该判断指针本身为空呢?
可以看这个遍历的指针最后需要走到哪里 需不需要对最后一个节点做操作

时间复杂度:O(n)
空间复杂度:O(1)

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

19. 删除链表的倒数第 N 个结点 - 力扣(LeetCode)
在这里插入图片描述

暴力求解

这道题最容易想出的方法是暴力求解,方法是先遍历一遍链表用计数器count数出有多少个链表节点,之后用count减去N,得到的就是正着数第count-N个节点就是我们要删除的节点,然后还是用之前删除链表的思路,先找到要删除节点的前一个节点的next指向要删除节点的next完成删除。

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode*hummyhead=new ListNode(0);
        hummyhead->next=head;
        ListNode*cur=hummyhead;int count=0;
        while(cur->next){
            cur=cur->next;count++;
        }
        int n1=(count-n)>0?count-n:0;cur=hummyhead;
        while(n1--){
            cur=cur->next;
        }
        if(count)
        cur->next=cur->next->next;
        return hummyhead->next;
    }
};

此题仍然使用虚拟头节点的方法来写,这样当要删除的节点是头节点的时候,并不需要单独判断情况,节省代码量。

双指针法

而另一种相对省时间一点的方法是双指针法

具体思路为创建快慢指针,一开始均指向虚拟头节点,然后让快指针先走n步,然后快慢再同时往后遍历,直到快指针指向空,此时慢指针所指向的节点则为要删除的节点。

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dummyHead = new ListNode(0);
        dummyHead->next = head;
        ListNode* slow = dummyHead;
        ListNode* fast = dummyHead;
        while(n-- && fast != NULL) {
            fast = fast->next;
        }
        fast = fast->next; // fast再提前走一步,因为需要让slow指向删除节点的上一个节点
        while (fast != NULL) {
            fast = fast->next;
            slow = slow->next;
        }
        
         ListNode *tmp = slow->next;//  C++释放内存的逻辑
         slow->next = tmp->next;
         delete tmp;
        
        return dummyHead->next;
    }
};

思路写到这里已经很清晰了,那么为什么我们要将n++之后再走呢?原因也很简单,是为了让快指针fast指向要删除节点的上一个位置,如果不明白这个快慢指针的思路,可以自行在纸上以画图的形式模拟一下。

采用虚拟头结点不需要单独判断头结点是否为空,并且确实很有必要,不然很容易想漏情况,以下是不用虚拟头结点的代码:

class Solution {
public:
    ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
    if(pListHead==nullptr)
	{
		return nullptr;
	}

	ListNode* front=pListHead;
	ListNode* rear=pListHead;

	while (k>0&&front) {
	k--;
	front=front->next;
	}

	while (front) {
	front=front->next;
	rear=rear->next;
	}

	return k>0?nullptr:rear;
    }
};

其他问题

● 链表问题的debug:可以在循环中逻辑执行前打一次输出语句,执行后打一次输出节点值val, 符合预期在增加打输出node.next.val(如果你需要知道后面连接是啥),空针就代表断链了或者其他错误了, 然后这样慢慢增加找, 还有一种就是利用手动画图debug学链表的时候经常画图设计简单case边界case来调试,现在写链表的题都是可以脑子有链表图

时间复杂度: O(n)
空间复杂度: O(1)

环形链表 II

142. 环形链表 II - 力扣(LeetCode)

在这里插入图片描述

环形链表的基础上,增加了需要判断环的入口的问题,首先是判断链表是否有环,有环的判断是简单的,用双指针方法都从头开始,快指针一次走两个节点,慢指针一次走一个节点,如果相遇则为有环,相遇了之后在相遇点处,用一个指针来指向(slow指针直接向后走也可以)相遇点,然后在头节点再用一个指针指向,两个指针一起走,相遇处则为环的入口。

为什么快慢指针,一定会在有环时相遇呢?如果没有环,那么快指针一定比慢指针走得快所以,不可能相遇,但是如果有环,两指针同时在环内,那就是快指针追逐慢指针,由于快指针一次走两步,慢指针一次走一步,进环后每次运动都相当于两指针距离减少一个节点,相当于慢指针不动,快指针每次走一步,所以有环两节点一定会相遇!但是如果快指针一次走更多步,比如走三步,那么有可能进入环之后,两指针无法相遇,造成死循环。

讲完相遇再说说,第二个关键的步骤,我们假设从起点到换入口距离为x,从入口到相遇点位置距离为y,相遇点再到入口的后半段距离我们设为z,可得慢指针slow走过x+y,而快指针fast走过x+y+n*(y+z),这里的n是fast指针在环内走的圈数,y+z是一圈经过的距离,可得(x+y)2=x+y+n(y+z)化简得x=n*(y+z)-y,单独提出一圈也就是y+z来消去-y得x=(n-1)*(y+z)+z。当n为一圈时,得到x=z的关系。

正是由于这样我们才得到了上述的结论,如果fast指针在圈内转了不止一圈,实际上结果也是一样的因为无论它转了多少圈,最后开始相遇的那一圈,起点是一样的。

如果对以上推理有些疑问,可以参考这篇博客,里面有详细的分析:【链表OJ题(九)】环形链表延伸问题以及相关OJ题

公式推导

这个方法的难点在于公式推导的过程,只要推导出了公式,解题就变得十分简单
结论:一个指针从 相遇点 开始走,一个指针从 链表头 开始走,它们会在 环的入口点 相遇。”
接下来推导公式:

在这里插入图片描述
由于 fast 的速度是 slow 的 2 倍。

所以便可以得出这个式子:2 ( L + x ) = L + N * c + x,而这个式子又可以进行推导:

2 ( L + x ) = L + N * c + x
            ↓
      L + x = N * c
            ↓
      L = N * c - x
            ↓
      L = ( N - 1 ) * c +  c - x 

这里 公式已经推导 完成:L = ( N - 1 ) * c + c - x 。但是这个公式到底是什么意思?

意思是一个指针从起始点开始走,一个指针从相遇点开始走,它们会在环的入口点相遇

根据这个我们也就可以做出这道题目了。

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode* fast = head;
        ListNode* slow = head;
        while(fast != NULL && fast->next != NULL) {
            slow = slow->next;
            fast = fast->next->next;
            // 快慢指针相遇,此时从head 和 相遇点,同时查找直至相遇
            if (slow == fast) {
                ListNode* index1 = fast;
                ListNode* index2 = head;
                while (index1 != index2) {
                    index1 = index1->next;
                    index2 = index2->next;
                }
                return index2; // 返回环的入口
            }
        }
        return NULL;
    }
};

相交链表

先利用 快慢指针 ,以 环形链表 的解法,找到 fast 和 slow 相交的点。然后将这个 交点 给为 meetnode 。作为两条新链表的尾。那么 meetnode->next 为某条新链表的头。然后 入环点 ,就可以看做是两条链表的交点。然后就是 相交链表 的做法
在这里插入图片描述

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode* curA = headA;
        ListNode* curB = headB;
        int lenA = 0, lenB = 0;
        while (curA != NULL) { // 求链表A的长度
            lenA++;
            curA = curA->next;
        }
        while (curB != NULL) { // 求链表B的长度
            lenB++;
            curB = curB->next;
        }
        curA = headA;
        curB = headB;
        // 让curA为最长链表的头,lenA为其长度
        if (lenB > lenA) {
            swap (lenA, lenB);
            swap (curA, curB);
        }
        // 求长度差
        int gap = lenA - lenB;
        // 让curA和curB在同一起点上(末尾位置对齐)
        while (gap--) {
            curA = curA->next;
        }
        // 遍历curA 和 curB,遇到相同则直接返回
        while (curA != NULL) {
            if (curA == curB) {
                return curA;
            }
            curA = curA->next;
            curB = curB->next;
        }
        return NULL;
    }
};

时间复杂度:O(n + m)
空间复杂度:O(1)

总结:

今天的三道题也算是复习回顾了,但对递归有了新的理解。接下来,我们继续进行算法练习·。希望我的文章和讲解能对大家的学习提供一些帮助。

当然,本文仍有许多不足之处,欢迎各位小伙伴们随时私信交流、批评指正!我们下期见~

在这里插入图片描述

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

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

相关文章

毫米波雷达 TI IWR1443 在 ROS 中进行 octomap 建图

个人实验记录 /mmwave_ti_ros/ros_driver/src/ti_mmwave_rospkg/launch/1443_multi_3d_0.launch <launch><!-- Input arguments --><arg name"device" value"1443" doc"TI mmWave sensor device type [1443, 1642]"/><arg…

一例“msvc编译器O2优化导致的崩溃”的分析

1. 初步分析 某进程崩溃必现。 打开崩溃dmp&#xff0c;结合c源代码&#xff0c;崩溃大致发生在某dll代码里的这句&#xff1a;SAFE_DELETE(pContentData); En_HP_HandleResult CTcpOperation::OnClintReceive(HP_Client pSender, HP_CONNID dwConnID, const BYTE * pdata, i…

组队竞赛(int溢出问题)

目录 一、题目 二、代码 &#xff08;一&#xff09;没有注意int溢出 &#xff08;二&#xff09;正确代码 1. long long sum0 2. #define int long long 3. 使用现成的sort函数 一、题目 二、代码 &#xff08;一&#xff09;没有注意int溢出 #include <iostream&g…

机器学习的主要内容

分类任务 回归任务 有一些算法只能解决回归问题有一些算法只能解决分类问题有一些算法的思路既能解决回归问题&#xff0c;又能解决分类问题 一些情况下&#xff0c; 回归任务可以转化为分类任务&#xff0c; 比如我们预测学生的成绩&#xff0c;然后根据学生的成绩划分为A类、…

js制作柱状图的x轴时间, 分别展示 月/周/日 的数据

背景 有个需求是要做一个柱状图, x 轴是时间, y 轴是数量. 其中 x 轴的时间有三种查看方式: 月份/周/日, 也就是分别查看从当前日期开始倒推的最近每月/每周/每日的数量. 本篇文章主要是用来制作三种不同的 x 轴 从当前月开始倒推月份 注意 getMonth() 函数可以获取当前月份…

【【萌新的FPGA学习之实战流水灯】】

萌新的FPGA学习之实战流水灯 实验任务 本节的实验任务是使用领航者底板上的两个 PL LED 灯顺序点亮并熄灭&#xff0c;循环往复产生流水灯的效 果&#xff0c;流水间隔时间为 0.5s。 1MHz&#xff1d;1000000Hz 10的6次方 1ns&#xff1d;10的-9次方秒 开发板晶振50Mhz 计算得…

如何看待Unity新的收费模式?

文章目录 背景Unity的论点开发者的担忧如何看待Unity新的收费模式&#xff1f;1. 理解Unity的立场2. 考虑小型开发者3. 探索替代方案4. 对市场变化保持敏感5. 提高游戏质量 结论 &#x1f389; 如何看待Unity新的收费模式&#xff1f; ☆* o(≧▽≦)o *☆嗨~我是IT陈寒&#x1…

R语言柱状图直方图 histogram

柱状图简介 柱状图也叫直方图&#xff0c;是展示连续性数值的分布状况。在x轴上将连续型数值分为一定数量的组&#xff0c;y轴显示对应值的频数。 R基本的柱状图 hist 我们用R自带的Orange数据来画图。 > head(Orange)Tree age circumference(圆周长) 1 1 118 …

Docker搭建DNS服务器--nouse

前言 DNS服务器是(Domain Name System或者Domain Name Service)域名系统或者域名服务,域名系统为Internet上的主机分配域名地址和IP地址。 安装 2.1 实验环境 IP 系统版本 角色 192.168.40.121 Ubuntu 22.10 DNS服务器 192.168.40.122 Ubuntu 22.10 测试机器 2.2 …

数据库选型参考

嵌入式数据库 SQLite、Berkeley DB、Derby、H2、HSQL DB SQLite&#xff1a; SQLite是一种非常流行的文件型数据库&#xff0c;它是一款轻量级、高性能、开源的嵌入式数据库引擎。SQLite采用C语言编写&#xff0c;可以在各种操作系统上运行&#xff0c;并且支持大多数标准SQL语…

csa从初阶到大牛(训练题1)

使用普通账户新建如下结构的2个目录&#xff1a; ~/t1/t2/t3/t4&#xff0c;~/m1/m2/m3/m4,并显示t1目录的详细信息&#xff0c;将/etc/passwd文件拷贝到~/t1/t2/t3目录下面&#xff0c;将~/t1/下面的内容拷贝到~/m1/m2/m/3/m4目录下面,最后删除~/t1/t2/t3下面的目录 # 创建目…

前端JavaScript入门到精通,javascript核心进阶ES6语法、API、js高级等基础知识和实战 —— JS基础(四)

开始吧&#xff0c;做时间的主人&#xff01; 把时间分给睡眠&#xff0c;分给书籍&#xff0c;分给运动&#xff0c; 分给花鸟树木和山川湖海&#xff0c; 分给你对这个世界的热爱&#xff0c; 而不是将自己浪费在无聊的人和事上。 思维导图 函数 为什么需要函数 <!DO…

pytest框架运行时的参数,以及多线程分布式运行用例、运行指定模块用例

一、运行时的参数 在上一篇博客中写了pytest最为核心的运行时前后置如何设置&#xff0c;细心的朋友可能也会发现其实我们当时就加过运行时的参数-vs。 pytest.main([‘-s’])&#xff1a;能打印出调试信息&#xff0c;print()或者日志都可以直接打印在控制台上。 pytest.ma…

栈和队列1——栈的实现及其oj(括号匹配问题)

一&#xff0c;栈的概念 栈是一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶&#xff0c;另一端称为栈底。栈中的数据元素遵守后进先出LIFO&#xff08;Last In First Out&#xff09;的原则。 压栈&#xf…

【100天精通Python】Day68:Python可视化_Matplotlib 绘制热力图,示例+代码

目录 1 值热力图&#xff08;Value Heatmap&#xff09;: 2 密度热力图&#xff08;Density Heatmap&#xff09; 3 时间热力图&#xff08;Time Heatmap&#xff09;: 4 空间热力图&#xff08;Spatial Heatmap&#xff09; 5 渐变热力图&#xff08;Gradient Heatmap&am…

C语言实现获取文件大小(字节数)

首先使用如下命令在当前文件夹&#xff0c;创建一个大小为1234578字节的空白文件&#xff1a; fsutil file createnew ./test.bin 12345678关于fsutil命令的介绍&#xff1a;Windows快速创建任意大小的空白文件 使用十六进制编辑器打开&#xff0c;可以看到内容全是0&#xf…

滴滴一面:线程池任务,如何设置优先级?

说在前面 在40岁老架构师 尼恩的读者交流群(50)中&#xff0c;最近有小伙伴拿到了一线互联网企业如滴滴、极兔、有赞、希音、百度、网易的面试资格&#xff0c;遇到很多很重要的面试题&#xff1a; 如何设计线程池&#xff1f;请手写一个简单线程池&#xff1f; 就在昨天&…

肖sir__mysql之综合题练习__013

数据库题&#xff08;10*5&#xff09; 下面是一个学生与课程的数据库&#xff0c;三个关系表为&#xff1a; 学生表S&#xff08;Sid&#xff0c;SNAME,AGE,SEX&#xff09; 成绩表SC&#xff08;Sid&#xff0c;Cid&#xff0c;GRADE&#xff09; 课程表C&#xff08;Cid&…

linux进程杀不死

项目场景&#xff1a; 虚拟机 问题描述 linux进程杀不死 无反应 原因分析&#xff1a; 进程僵死zombie 解决方案&#xff1a; 进proc或者find命令找到进程所在地址 cat status查看进程杀死子进程

【AI视野·今日CV 计算机视觉论文速览 第252期】Fri, 22 Sep 2023

AI视野今日CS.CV 计算机视觉论文速览 Fri, 22 Sep 2023 Totally 90 papers &#x1f449;上期速览✈更多精彩请移步主页 Interesting: &#x1f4da;SVGCustomization, 基于文本的矢量图生成定制(from 香港城市大学)。 website:https://intchous.github.io/SVGCustomization/ …