【Day41】143. 重排链表

文章目录

143.重排链表

题目:

给定一个单链表 L 的头节点 head ,单链表 L 表示为:
L0 → L1 → ... → Ln - 1 → Ln

请将其重新排列后变为:
L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → ...

不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

示例 1

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

示例 2

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

提示

链表的长度范围为 [1, 5 * 10^4]

1 <= node.val <= 1000


思路:

采用切分-反转-合并三步解法,复用链表基础操作,时间复杂度O(n),空间复杂度O(1)。

  1. 快慢指针找中点 :将链表切分为前后两段,奇数长度时前半段多一个节点
  2. 反转后半段链表 :解决单链表无法反向遍历的问题。
    反转链表
  3. 交叉合并两段链表:按前半段节点、后半段节点的顺序交替拼接。

示例1(偶数节点:1→2→3→4)

  1. 切分:前半段1→2,后半段3→4

  2. 反转后半段:4→3

  3. 合并结果:1→4→2→3

示例2(奇数节点:1→2→3→4→5)

  1. 切分:前半段1→2→3,后半段4→5

  2. 反转后半段:5→4

  3. 合并结果:1→5→2→4→3


代码实现(Go):

go 复制代码
package main

import "fmt"

// ListNode 定义单链表节点结构
type ListNode struct {
	Val  int
	Next *ListNode
}

// reorderList 重排链表主函数
// 要求:L0→Ln→L1→Ln-1→L2→Ln-2→...
func reorderList(head *ListNode) {
	// 边界条件:空链表或只有一个节点,无需处理
	if head == nil || head.Next == nil {
		return
	}

	// 1. 快慢指针寻找链表中点,并切分链表
	slow, fast := head, head.Next
	// 快指针走到末尾时,慢指针到达中点
	// 因为 fast 每次走两步,所以必须保证 fast 和 fast.Next 都不为 nil,避免空指针访问
	for fast != nil && fast.Next != nil {
		slow = slow.Next
		fast = fast.Next.Next
	}
	// 切分得到后半段头节点
	rightHead := slow.Next
	// 断开前后两段,形成独立链表
	slow.Next = nil

	// 2. 反转后半段链表
	rightHead = reverseList(rightHead)

	// 3. 交叉合并前后两段链表
	mergeList(head, rightHead)
}

// reverseList 反转单链表,返回反转后的头节点
func reverseList(head *ListNode) *ListNode {
	var prev *ListNode // 前驱节点,初始为nil
	cur := head       // 当前遍历节点
	for cur != nil {
		next := cur.Next // 保存下一个节点
		cur.Next = prev  // 反转当前节点指向
		prev = cur       // 前驱节点后移
		cur = next       // 当前节点后移
	}
	// prev 最终指向反转后的头节点
	return prev
}

// mergeList 交叉合并两个链表 l1 和 l2
// 合并规则:l1 节点 -> l2 节点 -> l1 节点 -> l2 节点...
func mergeList(l1, l2 *ListNode) {
	for l1 != nil && l2 != nil {
		// 暂存两个链表的下一个节点,防止断链
		next1, next2 := l1.Next, l2.Next

		// 执行交叉拼接
		l1.Next = l2
		l2.Next = next1

		// 两个链表指针同步后移
		l1, l2 = next1, next2
	}
}

func main() {
	// ========== 测试用例 1:偶数个节点 1->2->3->4 ==========
	// 构建链表:1 -> 2 -> 3 -> 4
	head1 := &ListNode{
		Val: 1,
		Next: &ListNode{
			Val: 2,
			Next: &ListNode{
				Val: 3,
				Next: &ListNode{Val: 4}, // 默认 Next=nil,表示尾节点
			},
		},
	}

	// 调用重排函数:变成 1->4->2->3
	reorderList(head1)

	// 遍历输出链表
	for cur := head1; cur != nil; cur = cur.Next {
		fmt.Print(cur.Val)
		if cur.Next != nil {
			fmt.Print("->") // 控制输出格式
		}
	}
	fmt.Println()

	// ========== 测试用例 2:奇数个节点 1->2->3->4->5 ==========
	// 构建链表:1 -> 2 -> 3 -> 4 -> 5
	head2 := &ListNode{
		Val: 1,
		Next: &ListNode{
			Val: 2,
			Next: &ListNode{
				Val: 3,
				Next: &ListNode{
					Val: 4,
					Next: &ListNode{Val: 5}, // 尾节点
				},
			},
		},
	}

	// 调用重排函数:变成 1->5->2->4->3
	reorderList(head2)

	// 遍历输出链表
	for cur := head2; cur != nil; cur = cur.Next {
		fmt.Print(cur.Val)
		if cur.Next != nil {
			fmt.Print("->")
		}
	}
}
  • 时间复杂度O(n),仅遍历链表常数次。
  • 空间复杂度O(1),仅使用常数级指针变量。

相关推荐
Zaly.5 小时前
【Python刷题】LeetCode 1727 重新排列后的最大子矩阵
算法·leetcode·矩阵
捧 花5 小时前
最小生成树算法(Go)
golang·最小生成树·kruskal·prim
添尹6 小时前
Go语言基础之数组
后端·golang
liurunlin8887 小时前
Go环境搭建(vscode调试)
开发语言·vscode·golang
memcpy07 小时前
LeetCode 1456. 定长子串中元音的最大数目【定长滑窗模板题】中等
算法·leetcode·职场和发展
玛丽莲茼蒿8 小时前
LeetCode hot100【相交链表】【简单】
算法·leetcode·职场和发展
wen__xvn8 小时前
力扣模拟题刷题
算法·leetcode
不要秃头的小孩8 小时前
力扣刷题——111.二叉树的最小深度
数据结构·python·算法·leetcode
We་ct8 小时前
LeetCode 35. 搜索插入位置:二分查找的经典应用
前端·算法·leetcode·typescript·个人开发