链表翻转问题

Page content

链表翻转相关题目

206. 翻转链表

所谓翻转,就是把 A -> B -> C,翻转后得到 C -> B -> A

单单讲,想要翻转一个节点,应该怎么做?首先要记录该节点的前一个节点prev,然后把当前节点head的next指针指向prev

所以说我们需要一个prev来记录要翻转的节点的前一个节点,这也是做链表题目的一个技巧,添加虚拟头指针,代码如下

// 常规解法,直接翻转
func reverseList(head *ListNode) *ListNode {
	var prev *ListNode
	for head != nil {
		next := head.Next // 要翻转head节点,先记录下head.Next,以免丢失
		head.Next = prev  // 翻转head,即是把head.Next 指向它的前一个节点prev
		prev = head       // prev 向后挪一个位置
		head = next       // head 向后挪一个位置
	}
	return prev
}

使用golang的语法糖,可以简写如下

func reverseList(head *ListNode) *ListNode {
	var prev *ListNode
	for head != nil {
		head.Next, head, prev = prev, head.Next, head
	}
	return prev
}

抽象一点的解法,使用递归

func reverseList(head *ListNode) *ListNode {
	if head == nil || head.Next == nil { // 递归出口,链表为空,或是只有一个节点,直接返回
		return head
	}

	// 递归调用翻转链表的方法,去翻转 head 以后的部分
	final := reverseList(head.Next)

	// head.Next 是上面递归调用翻转方法的入参,递归调用成功后,head.Next 将会变为最后一个节点
	// 也就是整个链表翻转完成后的倒数第二个节点
	// 整个链表翻转完成后,head 是最后一个节点
	// 所以把 head 链接到倒数第二个节点后
	head.Next.Next = head

	// head 是最后一个节点,Next 置空
	head.Next = nil
	return final
}

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

有了206题目做基础,现在我们想要2个一组翻转链表,首先需要知道要翻转的两个节点 a -> b,还要知道这两个节点的前一个节点 prev,翻转完成后的顺序是 prev -> b -> a

直接翻转

func swapPairs(head *ListNode) *ListNode {
	virtual := &ListNode{Next: head} // 添加一个虚拟头结点
	prev := virtual                  // prev 用于记录要翻转的两个节点的前一个节点 prev -> a -> b
	for head != nil && head.Next != nil {
		a, b := head, head.Next // 记录要翻转的两个节点 a, b
		next := b.Next          // 记录要翻转的两个节点的后一个节点,以免断链丢失
		prev.Next = b           // prev -> b,这里发生了断链,b 的下一个节点已经被保存到 next 变量
		b.Next = a              // b -> a 形成了 prev -> b -> a,翻转完成
		a.Next = next           // 重新链起来 prev -> b -> a -> next
		prev = a                // 移动prev到下一组要翻转的节点前面
		head = a.Next           // head移动到下一组要翻转的节点
	}
	return virtual.Next
}

利用golang的语法糖简写

func swapPairs(head *ListNode) *ListNode {
	virtual := &ListNode{Next: head}
	prev := virtual
	for prev.Next != nil && prev.Next.Next != nil {
		a, b := prev.Next, prev.Next.Next
		prev.Next, b.Next, a.Next, prev = b, a, b.Next, a
	}
	return virtual.Next
}

抽象一点,利用递归

func swapPairs(head *ListNode) *ListNode {
	if head == nil || head.Next == nil { // 递归出口
		return head
	}
	a, b := head, head.Next   // 要翻转的节点 a -> b
	next := swapPairs(b.Next) // b 之后的部分递归翻转,返回的头节点应该链到 b -> a 的后面
	b.Next, a.Next = a, next  // 翻转形成 b -> a -> next
	return b
}

简写

func swapPairs(head *ListNode) *ListNode {
	if head == nil || head.Next == nil { // 递归出口
		return head
	}
	b := head.Next
	head.Next.Next, head.Next = head, swapPairs(head.Next.Next)
	return b
}

92. 反转链表 II

题目描述:给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。

由前面的题目做基础,我们知道翻转链表应该怎么写,那翻转链表的前k(k<=节点数)个节点怎么写?

翻转链表的前k个节点

func reverseK(head *ListNode, k int) *ListNode {
	root := head // 保存一下第一个节点,翻转过后它将是翻转部分的最后一个节点
	var prev *ListNode
	for k > 0 {
		head.Next, head, prev = prev, head.Next, head
		k--
	}
	root.Next = head // 翻转部分和未翻转部分链接起来
	return prev
}

翻转链表的第left到第right部分节点,索引从1开始计数,不是0

func reverseBetween(head *ListNode, left int, right int) *ListNode {
	virtual := &ListNode{Next: head} // 添加一个虚拟头结点,方便处理 left 为 1 的情况
	prev := virtual                  // 记录要翻转部分的前一个节点
	for i := 1; i < left; i++ {      // prev 移动到要翻转部分的前一个节点
		prev = prev.Next
	}
	prev.Next = reverseK(prev.Next, right-left+1) // 翻转 left 开始到 right 部分共 right-left+1 个节点
	return virtual.Next
}

25. K 个一组翻转链表

题目描述:给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

大致思路:遍历k个节点,节点数足够,那么将这k个节点与后面的节点断开,前k个节点调用 reverseList() 函数翻转,后面的部分递归调用k个一组翻转的函数进行翻转

准备工作,怎样把链表按k个一组切分?

func cutK(head *ListNode, k int) []*ListNode {
	ret := make([]*ListNode, 0)
	if head == nil {
		return ret
	}
	prev := &ListNode{Next: head}
	for i := 0; i < k; i++ {
		prev = prev.Next
		if prev == nil { // 剩余节点不足k个,剩余的这些算一部分
			ret = append(ret, head)
			return ret
		}
	}
	ret = append(ret, head)
	ret = append(ret, cutK(prev.Next, k)...)
	prev.Next = nil
	return ret
}

k个一组翻转

func reverseKGroup(head *ListNode, k int) *ListNode {
	tail := &ListNode{Next: head} // 添加一个指针,和head组成一对儿,标记要翻转的k个节点组成的链表
	for i := 0; i < k; i++ {
		tail = tail.Next
		if tail == nil { // 不足k个,直接返回,不用翻转了
			return head
		}
	}
	next := reverseKGroup(tail.Next, k) // 翻转k个之后的部分
	tail.Next = nil                     // 断链,翻转这k个节点组成的链表
	reverseList(head)                   // 翻转过后,tail是第一个节点,head是最后一个节点
	head.Next = next
	return tail
}