链表问题:反转和判断是否有环

本文介绍了如何用Golang解决LeetCode中的链表问题,包括链表反转、两两交换节点、判断链表是否有环以及找到环形链表中尾节点连接的位置。通过定义链表节点结构,详细解析了每个问题的解决思路和算法实现,最后还提供了测试代码以验证算法的正确性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这里的问题都是来自于LeetCode,并且算法均由Golang实现。

  • 链表的反转
  • 两两交换链表中的节点
  • 判断链表中是否有环
  • 找到环形链表中尾节点连接到链表中的位置

首先需要定义链表节点的结构

type ListNode struct {
	Val  int
	Next *ListNode
}

一、链表的反转

两个节点指向反转,然后逐步后移

func reverseList(head *ListNode) *ListNode {

	if nil == head || nil == head.Next {
		return head
	}

	var pre *ListNode = nil
	cur := head
	var next *ListNode
	for cur != nil { // 当前节点不为空就可以参与反置工作,不需要要求next不为空
		next = cur.Next
		cur.Next = pre
		pre = cur
		cur = next
	}
	return pre
}

递归实现链表反转。当有n个节点,就链表而言,反转的问题可以理解为第n个节点完成了反转,然后考虑前n-1个节点的反转问题。所以递归实现如下:

/*
n1 --> n2 --> ... --> n(k-1) --> n(k) --> n(k+1) --> ... --> n(m) --> nil

	n(m) --> nil , n1 --> n2 --> ... --> n(k-1) --> n(k) --> n(k+1) --> ... --> n(m-1) --> n(m)
	n(m) --> n(m-1) --> nil, n1 --> n2 --> ... --> n(k-1) --> n(k) --> n(k+1) --> ... --> n(m-1)
	n(m) --> n(m-1) --> ... --> n(k+1) --> nil, n1 --> n2 --> ... --> n(k-1) --> n(k) --> n(k+1) :
		n(k).Next.Next = n(k) ----- n(k+1) --> n(k)
		n(k).Next = nil ----------- n(k+1) --> n(k) --> nil
*/
func reverseList1(head *ListNode) *ListNode {
	if nil == head || nil == head.Next { // 递归终止的位置
		return head
	}
	p := reverseList1(head.Next)
	head.Next.Next = head // 使得n(k+1) --> n(k)
	head.Next = nil // 避免出现 n(k+1) --> n(k) -->(k+1)

	return p
}

二、两两交换链表中的节点

算法思路:
1、每两个节点为一组,节点交换的时候需要记录这一组节点的上一个节点和下一个节点,即pre、next
2、pre不为空,则当前交换后的evenNode不是头节点,需要连接之前的节点,故pre.Next = evenNode;然后pre重新为交换后的oddNode
3、next为nil,总节点数为偶数个,已经交换完成,所以返回; next.Next为nil,总节点数为奇数个,最后一个多出的节点已经接在后面,没有交换操作,直接结束即可。

func swapPairs(head *ListNode) *ListNode {
	if nil == head || nil == head.Next {
		return head
	}

	oddNode := head       // 奇数节点
	evenNode := head.Next // 偶数节点
	newHead := evenNode
	var next *ListNode
	var pre *ListNode
	for evenNode != nil {
		// 交换操作
		next = evenNode.Next
		evenNode.Next = oddNode
		oddNode.Next = next
		// 两节点的之前节点pre的处理
		if pre != nil {
			pre.Next = evenNode
		}
		pre = oddNode
		// 两节点的之后节点next的处理
		oddNode = next
		if nil == next || nil == next.Next {
			break
		} else {
			evenNode = next.Next
		}
	}
	return newHead
}

三、判断链表中是否有环(尾节点是否有指向之前的任意节点)

判断链表是否有环,这里的环是指尾节点指向某个节点,所以就像是一个"9"型的路径,"1"型无环和"0"型有环均是特殊情况。
定义一个快指针和一个慢指针,快指针每次都比慢指针快一步,那么如果有环快指针一定会在环内与慢指针相遇。就像两个人长跑,快的人会遇到慢的人,这个时候快的人多跑了一圈

空间复杂度O(1),只使用了两个指针的空间
时间复杂度O(n), 最好的情况是链表没环,O(n+0) = O(n);如果有环,环的长度为K,O(n+k)

  • 最好的情况,环长度为1,O(n+1)
  • 最差的情况,尾节点指向头节点,O(n+n)
func hasCycle(head *ListNode) bool {
	if nil == head || nil == head.Next {
		return false
	}

	slow := head
	fast := head.Next
	for slow != fast {
		if nil == fast || nil == fast.Next {
			return false
		}

		slow = slow.Next
		fast = fast.Next.Next
	}
	return true
}

四、找到环形链表中尾节点连接到链表中的位置

问题描述(来自LeetCode):
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。

一种不太恰当的理解,链表的样子为“9”型,圆圈为链表形成的环,下部的端点为head节点。相交处为a节点(也是问题要求的节点),环中的某处为b节点(这个b节点的定义是,slow节点与fast节点在环中相遇的位置节点),实际上slow每次都比fast少走一步,而相遇时fast比。所以,2*(a+b) = l + (a+b),其中l是整数倍的环的长度。那么当slow在b节点处开始,head节点开始有一个slow1与slow同时开始一步一步走,那么这两个节点肯定会在a节点相遇。这样,返回相遇时的slow节点即可。

func detectCycle(head *ListNode) *ListNode {
	if nil == head || nil == head.Next {
		return nil
	}

	slow, fast := head, head // slow指针每次走一步,fast指针每次走两步
	hasCycle := false

	for nil != fast.Next && nil != fast.Next.Next {
		slow = slow.Next
		fast = fast.Next.Next

		if slow == fast {
			hasCycle = true
			break
		}
	}

	slow2 := head
	if hasCycle {
		for slow != slow2 {
			slow = slow.Next
			slow2 = slow2.Next
		}
		return slow
	} else {
		return nil
	}
}

五、简单的测试

下面是利用main函数测试上面实现的算法,以供参考。

func main() {
	node4 := ListNode{Val: 4, Next: nil}
	node3 := ListNode{Val: 3, Next: &node4}
	node2 := ListNode{Val: 2, Next: &node3}
	node1 := ListNode{Val: 1, Next: &node2}
	head := ListNode{Val: 0, Next: &node1}

	//newHead := reverseList(&head)
	//for newHead != nil {
	//	fmt.Println(newHead.Val)
	//	newHead = newHead.Next
	//}

	//newHead := reverseList1(&head)
	//for newHead != nil {
	//	fmt.Println(newHead.Val)
	//	newHead = newHead.Next
	//}

	newSwapHead := swapPairs(&head)
	for newSwapHead != nil {
		fmt.Println(newSwapHead.Val)
		newSwapHead = newSwapHead.Next
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值