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

【LeetCode每日一题】19. 删除链表的倒数第 N 个结点 24. 两两交换链表中的节点

每日一题

  • 19. 删除链表的倒数第 N 个结点
    • 题目
    • 总体思路
      • 算法步骤
      • 时间复杂度与空间复杂度
    • 代码
    • 知识点
      • 哑节点的核心作用
        • 1. 统一处理删除头节点的情况
        • 2. 避免空指针异常
        • 3. 简化指针操作
      • 哑节点的工作原理
        • 链表结构变化
      • 哑节点的其他应用场景
        • 1. 链表反转
        • 2. 链表合并
        • 3. 链表排序
  • 24. 两两交换链表中的节点
    • 题目
    • 总体思路
      • 算法步骤
      • 时间复杂度与空间复杂度
    • 代码

2025.9.1

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

题目

给你一个链表,删除链表的倒数第 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]

提示:

链表中结点的数目为 sz
1 <= sz <= 30
0 <= Node.val <= 100
1 <= n <= sz

进阶:你能尝试使用一趟扫描实现吗?

总体思路

这个算法采用快慢指针+哑节点的技巧来删除链表的倒数第n个节点。核心思想是让快指针先移动n步,然后快慢指针同时移动,当快指针到达链表末尾时,慢指针正好指向要删除节点的前一个节点。

算法步骤

  1. 创建哑节点:作为头节点的前驱,简化边界处理
  2. 快指针先移动n步:创建n个节点的间隔
  3. 同时移动快慢指针:直到快指针到达最后一个节点
  4. 删除目标节点:修改慢指针的next指向
  5. 返回结果:返回哑节点的下一个节点

时间复杂度与空间复杂度

  • 时间复杂度:O(L)
    • L 是链表的长度
    • 只需要遍历链表一次
    • 快指针最多移动 L 步
  • 空间复杂度:O(1)
    • 只使用了常数级别的额外空间(哑节点和两个指针)
    • 原地操作,不依赖链表长度

代码

golang

func removeNthFromEnd(head *ListNode, n int) *ListNode {dummy := &ListNode{Next: head}fast := dummyslow := dummyfor i:=0; i<n; i++ {fast = fast.Next}for fast.Next != nil {fast = fast.Nextslow = slow.Next}slow.Next = slow.Next.Nextreturn dummy.Next
}
/*** 删除链表的倒数第 N 个结点* 使用快慢指针和哑节点技巧来高效解决* * @param head *ListNode 链表的头节点* @param n int 要删除的倒数第n个节点的位置* @return *ListNode 删除节点后的新链表头节点*/
func removeNthFromEnd(head *ListNode, n int) *ListNode {// 创建哑节点(dummy node)作为头节点的前驱// 作用:1. 简化删除头节点的特殊情况 2. 统一操作逻辑dummy := &ListNode{Next: head}// 初始化快慢指针,都指向哑节点// fast: 快指针,用于创建间隔和定位链表末尾// slow: 慢指针,用于定位要删除节点的前一个位置fast := dummyslow := dummy// 第一阶段:快指针先移动n步// 这样快慢指针之间就保持了n个节点的间隔for i := 0; i < n; i++ {// 快指针向前移动一步// 由于有哑节点的存在,不需要担心空指针问题fast = fast.Next}// 第二阶段:快慢指针同时移动// 循环条件:快指针的下一个节点不为nil(即快指针不是最后一个节点)// 当循环结束时,快指针指向最后一个节点,慢指针指向倒数第n+1个节点for fast.Next != nil {fast = fast.Next  // 快指针移动一步slow = slow.Next  // 慢指针移动一步}// 删除操作:将慢指针的next指向下下一个节点// 此时slow指向倒数第n+1个节点,slow.Next就是要删除的倒数第n个节点// slow.Next.Next可能是nil(如果要删除的是最后一个节点)或有效的节点slow.Next = slow.Next.Next// 返回哑节点的下一个节点,即新链表的头节点// 如果删除的是原头节点,dummy.Next就是新的头节点return dummy.Next
}

知识点

哑节点的核心作用

1. 统一处理删除头节点的情况

没有哑节点

  • 删除头节点需要特殊处理
  • 需要额外的条件判断

有哑节点

  • 头节点变成第二个节点
  • 删除操作统一处理
2. 避免空指针异常
// 没有哑节点,可能访问空指针
if head == nil {return nil
}// 有哑节点,始终有有效的节点可以操作
dummy := &ListNode{Next: head}  // 即使head为nil,dummy也存在
3. 简化指针操作

删除头节点的对比

// 没有哑节点:需要特殊处理
if 要删除的是头节点 {return head.Next  // 直接返回第二个节点
}// 有哑节点:统一处理
slow.Next = slow.Next.Next  // 无论删除哪个节点,操作都一样
return dummy.Next           // 总是返回哑节点的下一个

哑节点的工作原理

链表结构变化

原始链表

head → node1 → node2 → node3 → nil

添加哑节点后:

dummy → head → node1 → node2 → node3 → nil

哑节点的其他应用场景

1. 链表反转
func reverseList(head *ListNode) *ListNode {dummy := &ListNode{}current := headfor current != nil {next := current.Nextcurrent.Next = dummy.Nextdummy.Next = currentcurrent = next}return dummy.Next
}
2. 链表合并
func mergeTwoLists(l1, l2 *ListNode) *ListNode {dummy := &ListNode{}tail := dummy// ...合并逻辑return dummy.Next
}
3. 链表排序
func sortList(head *ListNode) *ListNode {dummy := &ListNode{Next: head}// ...排序逻辑return dummy.Next
}

24. 两两交换链表中的节点

题目

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。

示例 1:
在这里插入图片描述

输入:head = [1,2,3,4]
输出:[2,1,4,3]

示例 2:

输入:head = []
输出:[]

示例 3:

输入:head = [1]
输出:[1]

提示:

链表中节点的数目在范围 [0, 100] 内
0 <= Node.val <= 100

总体思路

在这里插入图片描述
链表两两交换节点的核心难点是 指针重连顺序
如果直接在原链表上操作,需要同时修改 3 组指针,一不小心就会断链。

算法步骤

  1. 引入 虚拟头节点 dummy,使“头节点”也有前驱,简化边界判断。
  2. 每次循环只处理 两个节点(first、second)。
  3. 四步指针重连:
    • pre → second
    • first → second.Next
    • second → first
    • pre 前移到 first(下一轮的 pre)
  4. 循环条件:
    pre.Next != nil && pre.Next.Next != nil
    保证剩余节点至少有两个,避免空指针或单节点无法成对的问题。

时间复杂度与空间复杂度

时间复杂度 O(n) 每个节点被访问一次,常数级交换操作。
空间复杂度 O(1) 仅用若干指针变量,没有递归栈或额外切片。

代码

golang
一开始写的,并不好

/*** 两两交换链表中的节点* 将链表中每两个相邻节点交换位置* 示例: 1->2->3->4 变成 2->1->4->3* * @param head *ListNode 链表的头节点* @return *ListNode 交换后的链表头节点*/
func swapPairs(head *ListNode) *ListNode {// 边界条件处理:空链表或单节点链表直接返回if head == nil || head.Next == nil {return head}// 创建哑节点,简化头节点交换的处理// 哑节点的Next指向原链表的头节点dummy := &ListNode{Next: head}// 初始化三个指针:// pre - 指向当前要交换的两个节点的前一个节点// first - 指向要交换的第一个节点// second - 指向要交换的第二个节点pre := dummyfirst := pre.Nextsecond := first.Next// 循环交换节点,直到没有更多节点对可交换for first != nil && first.Next != nil {// 交换first和second节点:// 1. 将pre的Next指向second(将前驱节点连接到第二个节点)pre.Next = second// 2. 将first的Next指向second的Next(第一个节点连接到下一对节点)first.Next = second.Next// 3. 将second的Next指向first(第二个节点指向第一个节点,完成交换)second.Next = first// 移动指针到下一对要交换的节点:// pre移动到当前交换后的第二个节点(实际上是原来的第一个节点)pre = first// first移动到下一对的第一个节点first = pre.Next// 如果first为nil,说明没有更多节点了,退出循环if first == nil {break}// second移动到下一对的第二个节点second = first.Next}// 返回哑节点的下一个节点,即新链表的头节点return dummy.Next
}
/*** Definition for singly-linked list.* type ListNode struct {*     Val int*     Next *ListNode* }*/
func swapPairs(head *ListNode) *ListNode {if head == nil || head.Next == nil {return head}denny := &ListNode{Next: head}pre := dennyfirst := pre.Nextsecond := first.Nextfor first != nil && first.Next != nil {pre.Next = secondfirst.Next = second.Nextsecond.Next = firstpre = firstfirst = pre.Nextif first == nil {break}second = first.Next}return denny.Next
}

ai优化:

func swapPairs(head *ListNode) *ListNode {dummy := &ListNode{Next: head}pre := dummyfor pre.Next != nil && pre.Next.Next != nil {first := pre.Nextsecond := first.Nextpre.Next = secondfirst.Next = second.Nextsecond.Next = firstpre = first}return dummy.Next
}
/*** Definition for singly-linked list.* type ListNode struct {*     Val  int*     Next *ListNode* }*/func swapPairs(head *ListNode) *ListNode {// 1. 创建虚拟头节点,统一所有节点的操作方式dummy := &ListNode{Next: head}// 2. pre 指向“当前待交换节点对”的前驱pre := dummy// 3. 只要后面还有两个节点,就继续交换for pre.Next != nil && pre.Next.Next != nil {// 3-1 取出两个待交换节点first := pre.Next       // 第 1 个节点second := first.Next    // 第 2 个节点// 3-2 四步指针重连pre.Next = second       // 前驱指向 secondfirst.Next = second.Next // 第 1 个节点指向下一对的开头second.Next = first     // 第 2 个节点指向第 1 个节点// 3-3 pre 移动到“下一对”的前驱(即本轮 first)pre = first}// 4. dummy.Next 始终指向新头节点return dummy.Next
}
http://www.dtcms.com/a/361678.html

相关文章:

  • Java内存模型下的高性能锁优化与无锁编程实践指南
  • 几种特殊的数字滤波器---原理及设计
  • 【零碎小知识点 】(四) Java多线程编程深入与实践
  • MongoDB主从切换实战:如何让指定从库“精准”升级为主库?保姆级教程!
  • 36. Ansible变量+管理机密
  • 【Android】使用Handler做多个线程之间的通信
  • Java面试宝典:Redis高并发高可用(集群)
  • 函数,数组与正则表达式
  • Kafka 架构原理
  • 销售事业十年规划,并附上一套能帮助销售成长的「软件工具组合」
  • 【git 基础】detached HEAD state的出现和解决
  • C++11模板优化大揭秘:让你的代码更简洁、更安全、更高效
  • javaScript变量命名规则
  • 【汇客项目】:在启动过程中报错 本来安装的是node-sass 被卸载后安装的sass ,代码中一部分出现问题
  • 【深度学习基础】深度学习中的数据增强技术:从理论到实践的解析
  • 【ARMv7】开篇:掌握ARMv7架构Soc开发技能
  • Deepoc具身智能运动控制板:赋能机器感知与决策
  • (MySQL)分布式锁
  • CCNP考试通过后多久有证书,哪里可以查询下载电子证书。
  • 重新理解图神经网络训练:数据、Batch、权重与大图
  • 深入理解零拷贝:本地IO与网络IO的性能优化利器
  • wpf之StackPanel
  • 一、Git与Gitee常见问题解答
  • 2025年数字化转型关键证书分析与选择指南
  • Spark和Spring整合处理离线数据
  • 在idea当中git的基础使用
  • Ansible变量与机密管理总结
  • 人工智能学习:什么是NLP自然语言处理
  • 【自记录】Ubuntu20.04下Python自编译
  • 全栈智算系列直播 | 智算中心对网络的需求与应对策略(上)