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

【LeetCode】86. 分隔链表

文章目录

  • 86. 分隔链表
    • 题目描述
    • 示例 1:
    • 示例 2:
    • 提示:
    • 解题思路
      • 问题深度分析
        • 问题本质
        • 核心思想
        • 关键难点分析
        • 典型情况分析
        • 算法对比
      • 算法流程图
        • 主算法流程(双链表)
        • 分区处理流程
      • 复杂度分析
        • 时间复杂度详解
        • 空间复杂度详解
      • 关键优化技巧
        • 技巧1:双链表算法(最优解法)
        • 技巧2:数组算法
        • 技巧3:递归算法
        • 技巧4:优化版双链表
      • 边界情况处理
      • 测试用例设计
        • 基础测试
        • 简单情况
        • 特殊情况
        • 边界情况
      • 常见错误与陷阱
        • 错误1:tail指针更新错误
        • 错误2:分区连接错误
        • 错误3:边界值处理错误
      • 实战技巧总结
      • 进阶扩展
        • 扩展1:返回分区信息
        • 扩展2:支持多个分区值
        • 扩展3:保持原链表
      • 应用场景
    • 代码实现
    • 测试结果
    • 核心收获
    • 应用拓展
    • 完整题解代码

86. 分隔链表

题目描述

给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。

你应当 保留 两个分区中每个节点的初始相对位置。

示例 1:

在这里插入图片描述

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

示例 2:

输入:head = [2,1], x = 2
输出:[1,2]

提示:

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

解题思路

问题深度分析

这是经典的链表分区问题,也是双指针算法的典型应用。核心在于保持相对位置,在O(n)时间内将链表分为两部分。

问题本质

给定链表和特定值x,将链表分为两部分:小于x的节点在前,大于等于x的节点在后,并保持每个分区中节点的初始相对位置。

核心思想

双链表 + 分区处理

  1. 双链表:创建两个虚拟链表分别存储小于x和大于等于x的节点
  2. 分区处理:遍历原链表,根据节点值分配到对应分区
  3. 链表合并:将两个分区连接起来
  4. 位置保持:保持每个分区中节点的相对位置

关键技巧

  • 使用两个dummy节点简化边界处理
  • 使用两个tail指针记录分区尾部
  • 遍历原链表,根据值分配到对应分区
  • 最后连接两个分区
关键难点分析

难点1:相对位置的保持

  • 需要保持每个分区中节点的初始相对位置
  • 不能改变节点在原链表中的顺序
  • 需要按顺序遍历和分配节点

难点2:分区的处理

  • 需要准确判断节点属于哪个分区
  • 需要正确处理边界值x
  • 需要处理空分区的情况

难点3:链表的合并

  • 需要正确连接两个分区
  • 需要处理尾部节点的next指针
  • 需要返回正确的头节点
典型情况分析

情况1:一般情况

head = [1,4,3,2,5,2], x = 3
过程:
1. 小于3: [1,2,2]
2. 大于等于3: [4,3,5]
3. 合并: [1,2,2,4,3,5]
结果: [1,2,2,4,3,5]

情况2:简单情况

head = [2,1], x = 2
过程:
1. 小于2: [1]
2. 大于等于2: [2]
3. 合并: [1,2]
结果: [1,2]

情况3:全部小于x

head = [1,2,3], x = 5
结果: [1,2,3]

情况4:全部大于等于x

head = [4,5,6], x = 3
结果: [4,5,6]
算法对比
算法时间复杂度空间复杂度特点
双链表O(n)O(1)最优解法
数组O(n)O(n)空间复杂度高
递归O(n)O(n)空间复杂度高
暴力法O(n²)O(1)效率较低

注:n为链表长度

算法流程图

主算法流程(双链表)
开始: head, x
创建两个dummy节点
初始化两个tail指针
curr = head
curr != nil?
连接两个分区
判断curr.Val < x?
添加到小于分区
添加到大于等于分区
更新小于分区tail
更新大于等于分区tail
curr = curr.Next
返回合并后的链表
分区处理流程
遍历节点
节点值 < x?
添加到小于分区
添加到大于等于分区
更新小于分区tail
更新大于等于分区tail
移动到下一个节点
还有节点?
连接两个分区

复杂度分析

时间复杂度详解

双链表算法:O(n)

  • 遍历链表一次,每个节点处理一次
  • 分区操作和连接操作都是O(1)
  • 总时间:O(n)

数组算法:O(n)

  • 遍历链表一次,存储到数组
  • 重新构建链表
  • 时间复杂度相同
空间复杂度详解

双链表算法:O(1)

  • 只使用常数额外空间
  • 原地重新组织链表
  • 总空间:O(1)

关键优化技巧

技巧1:双链表算法(最优解法)
func partition(head *ListNode, x int) *ListNode {// 创建两个dummy节点dummy1 := &ListNode{} // 小于x的节点dummy2 := &ListNode{} // 大于等于x的节点tail1 := dummy1tail2 := dummy2curr := headfor curr != nil {if curr.Val < x {tail1.Next = currtail1 = tail1.Next} else {tail2.Next = currtail2 = tail2.Next}curr = curr.Next}// 连接两个分区tail1.Next = dummy2.Nexttail2.Next = nilreturn dummy1.Next
}

优势

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)
  • 保持相对位置
技巧2:数组算法
func partition(head *ListNode, x int) *ListNode {if head == nil {return nil}// 收集所有节点值var values []intcurr := headfor curr != nil {values = append(values, curr.Val)curr = curr.Next}// 重新排列var less, greater []intfor _, val := range values {if val < x {less = append(less, val)} else {greater = append(greater, val)}}// 重建链表allValues := append(less, greater...)if len(allValues) == 0 {return nil}newHead := &ListNode{Val: allValues[0]}curr = newHeadfor i := 1; i < len(allValues); i++ {curr.Next = &ListNode{Val: allValues[i]}curr = curr.Next}return newHead
}

特点:使用数组重新排列,空间复杂度高

技巧3:递归算法
func partition(head *ListNode, x int) *ListNode {if head == nil || head.Next == nil {return head}// 递归处理剩余部分head.Next = partition(head.Next, x)// 如果当前节点需要移动if head.Val >= x {// 找到第一个小于x的节点curr := head.Nextfor curr != nil && curr.Val >= x {curr = curr.Next}if curr != nil {// 将当前节点插入到正确位置head.Next = curr.Nextcurr.Next = headreturn head.Next}}return head
}

特点:使用递归,代码复杂但空间复杂度高

技巧4:优化版双链表
func partition(head *ListNode, x int) *ListNode {if head == nil {return nil}// 创建两个dummy节点dummy1 := &ListNode{}dummy2 := &ListNode{}tail1, tail2 := dummy1, dummy2for head != nil {if head.Val < x {tail1.Next = headtail1 = tail1.Next} else {tail2.Next = headtail2 = tail2.Next}head = head.Next}// 连接两个分区tail1.Next = dummy2.Nexttail2.Next = nilreturn dummy1.Next
}

特点:优化变量使用,代码更简洁

边界情况处理

  1. 空链表:返回nil
  2. 单节点:直接返回
  3. 全部小于x:返回原链表
  4. 全部大于等于x:返回原链表
  5. x为边界值:正确处理等于x的节点

测试用例设计

基础测试
输入: head = [1,4,3,2,5,2], x = 3
输出: [1,2,2,4,3,5]
说明: 一般情况
简单情况
输入: head = [2,1], x = 2
输出: [1,2]
说明: 简单分区
特殊情况
输入: head = [1], x = 0
输出: [1]
说明: 单节点情况
边界情况
输入: head = [], x = 0
输出: []
说明: 空链表情况

常见错误与陷阱

错误1:tail指针更新错误
// ❌ 错误:没有正确更新tail指针
if curr.Val < x {tail1.Next = curr// 忘记更新tail1
}// ✅ 正确:正确更新tail指针
if curr.Val < x {tail1.Next = currtail1 = tail1.Next
}
错误2:分区连接错误
// ❌ 错误:没有正确连接两个分区
tail1.Next = dummy2.Next
// 忘记设置tail2.Next = nil// ✅ 正确:正确连接两个分区
tail1.Next = dummy2.Next
tail2.Next = nil
错误3:边界值处理错误
// ❌ 错误:边界值处理不正确
if curr.Val < x { // 应该处理等于x的情况// 处理小于x的节点
}// ✅ 正确:正确处理边界值
if curr.Val < x {// 处理小于x的节点
} else {// 处理大于等于x的节点
}

实战技巧总结

  1. 双链表模板:两个dummy节点 + 两个tail指针
  2. 分区判断:准确判断节点属于哪个分区
  3. 指针更新:正确更新tail指针
  4. 分区连接:正确连接两个分区
  5. 边界处理:处理各种边界情况

进阶扩展

扩展1:返回分区信息
func partitionWithInfo(head *ListNode, x int) (*ListNode, int, int) {// 返回新链表和两个分区的节点数量// ...
}
扩展2:支持多个分区值
func partitionMultiple(head *ListNode, values []int) *ListNode {// 支持多个分区值// ...
}
扩展3:保持原链表
func partitionCopy(head *ListNode, x int) *ListNode {// 不修改原链表,返回新链表// ...
}

应用场景

  1. 数据处理:按条件分组数据
  2. 链表优化:重新组织链表结构
  3. 算法竞赛:链表操作基础
  4. 系统设计:数据分区
  5. 数据分析:数据分类

代码实现

本题提供了四种不同的解法,重点掌握双链表算法。

测试结果

测试用例双链表数组递归优化版
基础测试
简单情况
特殊情况
边界情况

核心收获

  1. 双链表算法:链表分区的经典应用
  2. 分区判断:准确判断节点属于哪个分区
  3. 指针更新:正确更新tail指针
  4. 分区连接:正确连接两个分区
  5. 边界处理:各种边界情况的考虑

应用拓展

  • 链表数据处理和分区
  • 算法竞赛基础
  • 系统设计应用
  • 数据分析技术
  • 内存优化技术

完整题解代码

package mainimport ("fmt"
)// ListNode 链表节点定义
type ListNode struct {Val  intNext *ListNode
}// =========================== 方法一:双链表算法(最优解法) ===========================func partition(head *ListNode, x int) *ListNode {// 创建两个dummy节点dummy1 := &ListNode{} // 小于x的节点dummy2 := &ListNode{} // 大于等于x的节点tail1 := dummy1tail2 := dummy2curr := headfor curr != nil {if curr.Val < x {tail1.Next = currtail1 = tail1.Next} else {tail2.Next = currtail2 = tail2.Next}curr = curr.Next}// 连接两个分区tail1.Next = dummy2.Nexttail2.Next = nilreturn dummy1.Next
}// =========================== 方法二:数组算法 ===========================func partition2(head *ListNode, x int) *ListNode {if head == nil {return nil}// 收集所有节点值var values []intcurr := headfor curr != nil {values = append(values, curr.Val)curr = curr.Next}// 重新排列var less, greater []intfor _, val := range values {if val < x {less = append(less, val)} else {greater = append(greater, val)}}// 重建链表allValues := append(less, greater...)if len(allValues) == 0 {return nil}newHead := &ListNode{Val: allValues[0]}curr = newHeadfor i := 1; i < len(allValues); i++ {curr.Next = &ListNode{Val: allValues[i]}curr = curr.Next}return newHead
}// =========================== 方法三:双链表算法(简化版) ===========================func partition3(head *ListNode, x int) *ListNode {// 创建两个dummy节点less := &ListNode{}greater := &ListNode{}lessPtr, greaterPtr := less, greaterfor head != nil {if head.Val < x {lessPtr.Next = headlessPtr = lessPtr.Next} else {greaterPtr.Next = headgreaterPtr = greaterPtr.Next}head = head.Next}greaterPtr.Next = nillessPtr.Next = greater.Nextreturn less.Next
}// =========================== 方法四:优化版双链表 ===========================func partition4(head *ListNode, x int) *ListNode {if head == nil {return nil}// 创建两个dummy节点dummy1 := &ListNode{}dummy2 := &ListNode{}tail1, tail2 := dummy1, dummy2for head != nil {if head.Val < x {tail1.Next = headtail1 = tail1.Next} else {tail2.Next = headtail2 = tail2.Next}head = head.Next}// 连接两个分区tail1.Next = dummy2.Nexttail2.Next = nilreturn dummy1.Next
}// =========================== 辅助函数 ===========================// 创建链表
func createList(vals []int) *ListNode {if len(vals) == 0 {return nil}head := &ListNode{Val: vals[0]}curr := headfor i := 1; i < len(vals); i++ {curr.Next = &ListNode{Val: vals[i]}curr = curr.Next}return head
}// 链表转数组
func listToArray(head *ListNode) []int {var result []intcurr := headfor curr != nil {result = append(result, curr.Val)curr = curr.Next}return result
}// 比较两个链表是否相等
func compareLists(l1, l2 *ListNode) bool {curr1, curr2 := l1, l2for curr1 != nil && curr2 != nil {if curr1.Val != curr2.Val {return false}curr1 = curr1.Nextcurr2 = curr2.Next}return curr1 == nil && curr2 == nil
}// =========================== 测试代码 ===========================func main() {fmt.Println("=== LeetCode 86: 分隔链表 ===\n")testCases := []struct {nums   []intx      intexpect []int}{{[]int{1, 4, 3, 2, 5, 2},3,[]int{1, 2, 2, 4, 3, 5},},{[]int{2, 1},2,[]int{1, 2},},{[]int{1},0,[]int{1},},{[]int{},0,[]int{},},{[]int{1, 2, 3},5,[]int{1, 2, 3},},{[]int{4, 5, 6},3,[]int{4, 5, 6},},{[]int{3, 1, 2},3,[]int{1, 2, 3},},{[]int{1, 4, 3, 0, 2, 5, 2},3,[]int{1, 0, 2, 2, 4, 3, 5},},}fmt.Println("方法一:双链表算法(最优解法)")runTests(testCases, partition)fmt.Println("\n方法二:数组算法")runTests(testCases, partition2)fmt.Println("\n方法三:双链表算法(简化版)")runTests(testCases, partition3)fmt.Println("\n方法四:优化版双链表")runTests(testCases, partition4)
}func runTests(testCases []struct {nums   []intx      intexpect []int
}, fn func(*ListNode, int) *ListNode) {passCount := 0for i, tc := range testCases {input := createList(tc.nums)expected := createList(tc.expect)result := fn(input, tc.x)status := "✅"if !compareLists(result, expected) {status = "❌"} else {passCount++}fmt.Printf("  测试%d: %s\n", i+1, status)if status == "❌" {fmt.Printf("    输入: %v, x=%d\n", tc.nums, tc.x)fmt.Printf("    输出: %v\n", listToArray(result))fmt.Printf("    期望: %v\n", tc.expect)}}fmt.Printf("  通过: %d/%d\n", passCount, len(testCases))
}
http://www.dtcms.com/a/516674.html

相关文章:

  • Python实现Jenkins实现自动化执行Job
  • 响应式网站建设服务企业建设企业网站的好处
  • 黑龙江省机场建设集团官网网站wordpress jquery 无法
  • 中国站免费推广入口江门移动网站建设多少钱
  • 打印机内容左右偏移调整指南
  • K 线形态 - 红三兵
  • PINNs for Medical Image Analysis: A Survey
  • 网站建设公司市场开发方案劳动法24小时免费咨询
  • 微信开发 网站备案吗建e网模型下载
  • Spring Boot 3零基础教程,WEB 开发 Spring Boot 错误处理机制 自定义异常页面 笔记46
  • 织梦网站入侵python 编辑wordpress
  • 青海省建设工程在哪个网站发布做网络推广一个月多少钱
  • SQL SERVER 解析XML
  • 算法偏见的社会建构与司法决策中的公平性规制
  • 企业培训笔记:外卖平台后端--套餐管理模块--新建套餐信息
  • 湖北 网站 备案 时间毕业设计做系统网站
  • h5游戏免费下载:骑士冒险
  • 河北城乡建设官网站做群头像的网站在线
  • 操作系统4.1.9 文件保护
  • 【图像算法 - 29】手把手教你用 YOLO + PyQt5 搭建垃圾检测系统(附完整源码)
  • 误差优化方向-1
  • 网站建设制作公司思企互联兰州网站优化公司
  • 沈阳平台网站建设网站开发的实训周的实训过程
  • 希尔排序详解
  • 专业网站开发公司网站流量排名 全球
  • 深入浅出 SQL 注入
  • wordpress 整站 数据石林网站建设
  • 哪些网站是响应式网站网站运营是做什么的
  • TDengine 配置参数作用范围对比
  • DDPM(Diffusion)个人总结