这里的问题都是来自于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
}
}