【From C To Go】2.1 链表刷题(持续更新)


记录一些链表相关的算法题的解法


更新记录:

日期内容
2024/05/22链表定义,反转链表,成对反转链表

0 准备

0.1 链表结构定义

type ListNode struct {
	Val  int
	Next *ListNode
}

0.2 工具函数

0.2.1 生成长度为n,值随机的单链表
func GenerateList(n int) *ListNode {
	var head *ListNode
	for i := 0; i < n; i++ {
		node := &ListNode{Val: rand.Intn(100)}
		if head == nil {
			head = node
		} else {
			node.Next = head
			head = node
		}
	}
	return head
}
0.2.2 遍历输出链表
func PrintList(head *ListNode) {
	for head != nil {
		print(head.Val, " ")
		head = head.Next
	}
	println()
}

1 反转单链表

1.1 问题描述

给定一个单链表的头结点pHead(该头节点是有值的,比如在下图,它的val是1),长度为n,反转该链表后,返回新链表的表头。

数据范围:
0≤n≤1000
要求:空间复杂度 O(1) ,
时间复杂度 O(n) 。

如当输入链表{1,2,3}时,
经反转后,原链表变为{3,2,1},所以对应的输出为{3,2,1}。

1.2 迭代方式

1.2.1 思路描述
  1. 需要声明两个指针类型变量,cur指向当前链表节点(初始为head),prev存放该链表节点的前置节点
  2. 迭代方式求解问题就像剥洋葱,一层一层地解决问题,问题的规模会随着迭代的深入变得越来越小,当满足某种终止条件时,最后的问题得到解决,也就意味着解决了全部问题
  3. 针对此问题,由于cur初始时指向链表头结点,因此迭代解决该问题其实就是从头结点开始,将cur的前置节点变为后置节点,同时将后置节点变为前置节点,然后对cur进行迭代(执行下一个节点),直至cur为空值(意味着链表走到了尽头)
1.2.2 代码
// 迭代方式反转链表
func ReverseListIter( head *ListNode ) *ListNode {
    cur := head // 需要迭代的主体
	var prev *ListNode // 迭代过程中需要记录的上一个节点
	for cur!= nil { 
		// 迭代过程中,记录当前节点的下一个节点,避免丢失
		tmp := cur.Next
		// 迭代过程中,将当前节点的下一个节点指向上一个节点
		cur.Next = prev
		// 迭代过程中,将上一个节点指向当前节点
		prev = cur
		// 迭代过程中,将当前节点指向下一个节点
		cur = tmp
	}
	// 迭代完成后,将头节点指向最后一个节点
	head = prev
	return head
}

1.2.3 验证
func main() {
	head := GenerateList(10)
	PrintList(head)
	PrintList(ReverseListIter(head))
}

运行结果:
在这里插入图片描述

1.3 递归方式

1.3.1 思路描述
  1. 递归的基本情况是:长度为0或1的链表,反转的结果是其本身
  2. 否则,对于长度为n的链表,反转该链表相当于将该链表的头结点拼接到第2个节点到第n个节点组成的长度为n-1的链表反转后的链表尾结点之后
  3. 递归进行第2步直到达到基本情况
1.3.2 代码
func ReverseListRecur(head *ListNode) *ListNode {
	// 递归终止条件
	if head == nil || head.Next == nil {
		return head
	}
	// 递归反转剩余部分
	newHead := ReverseListRecur(head.Next)

	// 反转当前节点
	head.Next.Next = head
	head.Next = nil

	return newHead
}
1.3.3 验证
func main() {
	head := GenerateList(10)
	PrintList(head)
	PrintList(ReverseListRecur(head))
}

运行结果:
在这里插入图片描述

2 成对反转链表

2.1 描述

一对一对的反转链表内的结点
For example,
Given1->2->3->4, return 2->1->4->3.

2.2 迭代方式

2.2.1 思路
  1. 使用一个虚拟头节点 dummy,指向链表的头节点。这有助于处理边界情况。
  2. 使用三个指针:prev(指向每对节点的前一个节点,初始指向 dummy),first(指向每对节点中的第一个节点),second(指向每对节点中的第二个节点)。
  3. 逐步交换每对节点,并更新指针以处理下一对节点。
2.2.2 代码
func ReverseListPairIter(head *ListNode) *ListNode {
	dummy := &ListNode{Next: head}
	prev := dummy

	for head != nil && head.Next != nil {
		first := head
		second := head.Next

		// 交换节点
		first.Next = second.Next
		second.Next = first
		prev.Next = second

		// 迭代
		prev = first
		head = first.Next
	}
	return dummy.Next
}
2.2.3 验证
func main() {
	head := GenerateList(11)
	PrintList(head)
	PrintList(ReverseListPairIter(head))
}

运行结果:
在这里插入图片描述

2.3 递归方式

2.3.1 思路
  1. 基本情况:如果链表为空或只有一个节点,不需要交换,直接返回头节点。
  2. 递归处理剩余链表的交换操作。
  3. 交换当前两节点,然后将递归处理的结果连接到交换后的节点后面。
2.3.2 代码
func ReverseListPairRecur(head *ListNode) *ListNode {
	// 基本情况
	if head == nil || head.Next == nil {
		return head
	}
	tmp := head.Next
	head.Next = ReverseListPairRecur(tmp.Next)
	tmp.Next = head
	return tmp
}
2.3.3 验证
func main() {
	head := GenerateList(11)
	PrintList(head)
	PrintList(ReverseListPairRecur(head))
}

运行结果:
在这里插入图片描述

  • 11
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值