首页 > 其他分享 >代码随想录训练营 Day4打卡 链表part02 24. 两两交换链表中的节点 19.删除链表的倒数第N个节点 面试题 02.07. 链表相交 142.环形链表II

代码随想录训练营 Day4打卡 链表part02 24. 两两交换链表中的节点 19.删除链表的倒数第N个节点 面试题 02.07. 链表相交 142.环形链表II

时间:2024-07-21 20:00:21浏览次数:14  
标签:head 随想录 fast next 链表 节点 指针

代码随想录训练营 Day4打卡 链表part02

一、 力扣24.两两交换链表中的节点

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
示例 1:
输入:head = [1,2,3,4]
输出:[2,1,4,3]

算法思路:
  1. 引入虚拟头结点:创建一个虚拟头结点 dummyHead,它的 next 指向原始链表头,简化边界处理。

  2. 初始化指针:cur 初始化为 dummyHead,用于遍历链表。

  3. 交换节点:在链表中两两交换节点,直到链表尾部。

  4. 使用临时变量 tmp 和 tmp1 来辅助交换操作。

  5. 通过三步更新指针,完成相邻节点的交换。
    在这里插入图片描述
    在这里插入图片描述

  6. 返回结果:交换完成后,dummyHead->next 即为新链表头,返回之。

版本一:迭代实现

实现思路:

  1. 创建哑节点指向链表头,以处理头节点交换。
  2. 遍历链表,每次检查并交换成对的节点。
  3. 交换完成后,返回哑节点的下一个节点作为新的头节点。
class Solution:
    def swapPairs(self, head: ListNode) -> ListNode:
        dummy_head = ListNode(next=head)
        current = dummy_head
        
        # 必须有cur的下一个和下下个才能交换,否则说明已经交换结束了
        while current.next and current.next.next:
            temp = current.next # 保存当前节点的下一个节点
            temp1 = current.next.next.next # 保存当前节点的下下个节点的下一个节点
            
            current.next = current.next.next # 交换:current.next指向下下个节点
            current.next.next = temp # 交换:新的current.next.next指向原来的current.next
            temp.next = temp1 # 交换:原来的current.next.next指向temp1
            
            current = current.next.next # 移动到下一对节点准备交换
        return dummy_head.next

版本二:递归实现

实现思路:

  1. 递归终止条件为链表为空或只有一个节点。
  2. 交换当前的两个节点,并递归处理后续节点。
  3. 返回新的头节点。
class Solution:
    def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
        if head is None or head.next is None:
            return head

        # 待翻转的两个node分别是pre和cur
        pre = head
        cur = head.next
        next = head.next.next
        
        cur.next = pre  # 交换:cur.next指向pre
        pre.next = self.swapPairs(next) # 递归处理next作为新头节点的链表,并赋值给pre.next
         
        return cur # 返回新的头节点cur

力扣题目链接
题目文章讲解
题目视频讲解

二、力扣19. 删除链表的倒数第N个节点

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
在这里插入图片描述
示例 1:
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]

算法思路

为了删除链表中倒数第N个节点,采用双指针法

  1. 引入虚拟头结点:简化边界处理,使删除操作统一。
    在这里插入图片描述

  2. 初始化双指针:fast 和 slow 均指向虚拟头结点。

  3. fast 先行:fast 指针先移动N+1步,拉开与slow的距离。
    在这里插入图片描述

  4. 同步移动:两指针同时前进,直至fast到达链表尾部。
    在这里插入图片描述

  5. 删除节点:此时slow指向目标节点的前驱,可安全删除倒数第N个节点。
    在这里插入图片描述
    这种方法避免了多次遍历或额外数据结构的使用,提高了效率。

代码实现
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next

class Solution:
    def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
        # 创建一个虚拟节点,并将其下一个指针设置为链表的头部
        dummy_head = ListNode(0, head)
        
        # 创建两个指针,慢指针和快指针,并将它们初始化为虚拟节点
        slow = fast = dummy_head
        
        # 快指针比慢指针快 n+1 步
        for i in range(n+1):
            fast = fast.next
        
        # 移动两个指针,直到快速指针到达链表的末尾
        while fast:
            slow = slow.next
            fast = fast.next
        
        # 通过更新第 (n-1) 个节点的 next 指针删除第 n 个节点
        slow.next = slow.next.next
        
        return dummy_head.next
        

力扣题目链接
题目文章讲解
题目视频讲解


三、面试题 02.07. 链表相交

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。
图示两个链表在节点 c1 开始相交:
题目数据 保证 整个链式结构中不存在环。
在这里插入图片描述
注意,函数返回结果后,链表必须 保持其原始结构 。
示例一:
在这里插入图片描述
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at ‘8’
解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。
在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。

算法思路(一):

步骤1:确定链表长度差
首先,我们需要确定两个链表的长度差。为此,我们初始化两个指针 curA 和 curB 分别指向链表 A 和 B 的头节点。我们遍历这两个链表直到它们的末尾,同时计数每个链表的节点数量。这样我们就能知道链表 A 和 B 的长度。
在这里插入图片描述

步骤2:对齐较短的链表
一旦我们知道了两个链表的长度差,我们就可以将较长链表的指针向前移动这个差值,这样两个指针就会处于同一水平线上,即它们距离各自链表的末尾有相同的距离。
在这里插入图片描述
步骤3:同步移动并查找交点
接下来,我们同步移动 curA 和 curB。由于它们现在处于对齐状态,如果链表有交点,那么当 curA 和 curB 指向同一个节点时,我们就找到了交点。我们只需要遍历一次剩余的链表即可找到交点。

版本一:求长度,同时出发

实现思路:

  1. 分别计算链表A和链表B的长度。
  2. 通过移动较长链表的头,使得两个链表在同一起点开始遍历。
  3. 同时遍历两个链表,当遇到相同节点时返回该节点,否则返回None。
class Solution:
    def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
        lenA, lenB = 0, 0
        cur = headA
        while cur:         # 求链表A的长度
            cur = cur.next 
            lenA += 1
        cur = headB 
        while cur:         # 求链表B的长度
            cur = cur.next 
            lenB += 1
        curA, curB = headA, headB
        if lenA > lenB:     # 让curB为最长链表的头,lenB为其长度
            curA, curB = curB, curA
            lenA, lenB = lenB, lenA 
        for _ in range(lenB - lenA):  # 让curA和curB在同一起点上(末尾位置对齐)
            curB = curB.next 
        while curA:         #  遍历curA 和 curB,遇到相同则直接返回
            if curA == curB:
                return curA
            else:
                curA = curA.next 
                curB = curB.next
        return None 

版本二:求长度,同时出发(代码复用 + 精简)

实现思路:

  1. 计算两个链表长度的差值。
  2. 通过辅助方法moveForward移动较长链表的头节点,使两链表长度相等。
  3. 同时遍历两个链表,找到相交节点。
class Solution:
    def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
        dis = self.getLength(headA) - self.getLength(headB)
        
        # 通过移动较长的链表,使两链表长度相等
        if dis > 0:
            headA = self.moveForward(headA, dis)
        else:
            headB = self.moveForward(headB, abs(dis))
        
        # 将两个头向前移动,直到它们相交
        while headA and headB:
            if headA == headB:
                return headA
            headA = headA.next
            headB = headB.next
        
        return None
    
    def getLength(self, head: ListNode) -> int:
        length = 0
        while head:
            length += 1
            head = head.next
        return length
    
    def moveForward(self, head: ListNode, steps: int) -> ListNode:
        while steps > 0:
            head = head.next
            steps -= 1
        return head

算法思路(二)

假设有两个链表A和B,它们在某个节点处相交。链表A的长度为m,链表B的长度为n。假设相交节点之前,链表A有a个节点,链表B有b个节点,且相交部分长度为c。则有:
m=a+c
n=b+c

使用两个指针pointerA和pointerB分别遍历链表A和链表B。当一个指针到达链表末尾时,将其重定位到另一个链表的头部。这样做的目的是使两个指针在相同的步数之后都到达相交节点

在这种遍历方式下,pointerA和pointerB在第二次遍历时会在相交节点相遇。
具体来说,当两个指针都遍历完各自的链表,并切换到对方的链表头部时,它们实际走过的节点数是相等的。
设pointerA和pointerB走的步数分别为L1和L2:
pointerA走的步数:a + c + b
pointerB走的步数:b + c + a
由于a + b + c是固定的,所以在相交点处两个指针会相遇。

版本三:等比例法

实现思路:

  1. 初始化两个指针,分别指向两个链表的头。
  2. 同时遍历两个链表,当一个指针到达链表末尾时,指向另一个链表的头。
  3. 当两个指针相等时,返回相交节点。
class Solution:
    def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
        # 处理边缘情况
        if not headA or not headB:
            return None
        
        # 在每个链表的头部初始化两个指针
        pointerA = headA
        pointerB = headB
        
        # 遍历两个链表直到指针相交
        while pointerA != pointerB:
            # 将指针向前移动一个节点
            pointerA = pointerA.next if pointerA else headB
            pointerB = pointerB.next if pointerB else headA
        
        # 如果相交,指针将位于交点节点,如果没有交点,值为None
        return pointerA

力扣题目链接
题目文章讲解

四、环形链表II

给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
示例 1:
在这里插入图片描述
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。

算法思想
  • 初始化:在链表中同时放置两个指针,slow 和 fast,均指向链表头部。
  • 移动策略:slow 每次向前移动一个节点,而 fast 每次向前移动两个节点。
  • 环检测:如果链表中存在环,由于 fast 移动速度是 slow 的两倍,它最终会追上 slow。如果两者相遇,则证明链表有环;如果
    fast 达到链表尾部(即 fast 或 fast->next 是 nullptr),则表明链表无环。

为何会相遇?
这是因为fast是走两步,slow是走一步,其实相对于slow来说,fast是一个节点一个节点的靠近slow的,所以fast一定可以和slow重合。
在这里插入图片描述
如果有环,如何找到这个环的入口
假设从头结点到环形入口节点 的节点数为x。 环形入口节点到 fast指针与slow指针相遇节点 节点数为y。 从相遇节点 再到环形入口节点节点数为 z。 如图所示:

在这里插入图片描述

慢指针走过的节点数: x + y
快指针走过的节点数: x + y + n (y + z)
其中

标签:head,随想录,fast,next,链表,节点,指针
From: https://blog.csdn.net/qq_42952637/article/details/140585935

相关文章

  • 《算法笔记》总结No.10——链表
        从第10期破例插叙一期单链表的实现,这个东东相当重要!考研的同学也可以看:相较于王道考研的伪码不太相同,专注于可以运行。如果是笔试中的伪码,意思正确即可~ 注:博主之前写过一个版本的顺序表和单链表的C++实现,和这篇的写法有所不同,不过内容也较全,大家可以先行阅读~......
  • javascript中常规操作节点的方法
    JavaScript常用操作DOM节点的方法包括获取节点、创建节点、添加节点、删除节点、替换节点等。1.获取节点(1)通过ID获取使用document.getElementById(“元素ID”)方法,通过元素的ID获取单个元素。这是最常用的方法之一,因为ID在页面中是唯一的,可以直接定位到具体元素。<d......
  • 代码随想录算法训练营第16天 | 二叉树更加进阶
    2024年7月18日用层序遍历巧解题513.找树左下角的值层序遍历的板子一定要熟背。classSolution{publicintfindBottomLeftValue(TreeNoderoot){List<List<Integer>>res=newArrayList<>();//用根左右遍历,每次记录高度intheight=0;......
  • 代码随想录算法训练营第15天 | 二叉树进阶
    2024年7月17日平衡二叉树/***Definitionforabinarytreenode.*publicclassTreeNode{*intval;*TreeNodeleft;*TreeNoderight;*TreeNode(){}*TreeNode(intval){this.val=val;}*TreeNode(intval,TreeNodeleft......
  • 在ROS2 - humble 中做一个节点
    ROS2(RobotOperatingSystem2)中的节点(Node)是ROS2系统中的一个核心概念,它代表了执行特定任务的进程或程序模块。节点定义:    在ROS2中,节点是系统中最小的处理单元,负责执行特定的任务或功能。每个节点都具有独立的处理能力和通信能力,可以与其他节点进行交互。功能: ......
  • 代码随想录算法训练营第34天 | 贪心算法 5:56.合并区间、738.单调递增的数字
    56.合并区间https://leetcode.cn/problems/merge-intervals/description/代码随想录https://programmercarl.com/0056.合并区间.html738.单调递增的数字https://leetcode.cn/problems/monotone-increasing-digits/description/代码随想录https://programmercarl.com/0738.......
  • 初阶数据结构的实现2 双向链表
    1.双向链表1.1概念与结构1.2实现双向链表1.2.1定义程序目标#define_CRT_SECURE_NO_WARNINGS1#pragmaonce#include<stdio.h>#include<assert.h>#include<stdlib.h>#include<stdbool.h>typedefintLTDateType;//定义双向链表结构typedefstructListNode{......
  • 代码随想录算法训练营第35天 | 动态规划1:509.斐波那契数、70.爬楼梯、746.使用最小花
    代码随想录算法训练营第35天|动态规划理论基础https://programmercarl.com/动态规划理论基础.html#算法公开课509.斐波那契数https://leetcode.cn/problems/fibonacci-number/submissions/548309803/代码随想录https://programmercarl.com/0509.斐波那契数.html#算法公开......
  • 【软考】数据结构与算法基础 - 数组和链表
    一、数组和链表的区别(很简单,但是很常考,记得要回答全面)什么是数组:C++语言中,可以用数组,处理一组数据类型相同的数据,不可以动态定义数组的大小(使用前,必须指定大小。)在实际应用中,用户使用数组之前,无法确定数组的大小只能够将数组定义成足够大小,多余出来空间可能不被使用,......
  • NoneType 在链表中不可下标
    我正在开发一个基本推荐软件的组合项目,其中我收集用户输入并根据这些输入向他们提供推荐列表。我正在使用链表数据结构,并且我可以获得程序的一部分来运行。但是,我目前遇到了一个似乎无法解决的错误。这是我遇到的问题:Traceback(mostrecentcalllast):File"/Use......