概念
刷题
24 两辆交换链表中的节点
使用虚拟头节点,解题思路:
19 删除链表的倒数第N个节点
package main
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func removeNthFromEnd(head *ListNode, n int) *ListNode {
dummyNode := &ListNode{0, head}
slow, fast := dummyNode, dummyNode
// 删除一个节点需要知道前一个节点的Next,所以这里是n+1,slow是被删除节点的前一个节点
for i:=0;i<n+1 && fast!=nil;i++ {
fast = fast.Next
}
// 链表中的节点数目为sz
// 1 <=n <= sz, 不需要考虑n超出sz的情况
for fast != nil {
slow = slow.Next
fast = fast.Next
}
slow.Next = slow.Next.Next
return dummyNode.Next
}
面试题 02.07 链表相交
解题思路:
判断2个链表是否相交,链表A和链表B
首先判断链表A,B长度是否为0,否则输出nil
分别遍历链表A,B。遍历A结束时遍历B,遍历B结束时遍历A,找到相同的节点返回
代码:
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func getIntersectionNode(headA, headB *ListNode) *ListNode {
if headA == nil || headB == nil {
return nil
}
tmpA := headA
tmpB := headB
for tmpA != nil || tmpB != nil {
if tmpA == nil {
tmpA = headB
}
if tmpB == nil {
tmpB = headA
}
if tmpA == tmpB {
return tmpA
}
tmpA = tmpA.Next
tmpB = tmpB.Next
}
return nil
}
142 环形链表II
使用hash map来记录每一次遍历的元素,代码简单,但是hash map需要消耗内存,每一次加入新的元素需要进行一次hash查找。
使用快慢指针
代码:
package main
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func detectCycle(head *ListNode) *ListNode {
slow := head
fast := head
var result *ListNode
if fast == nil {
return result
}
for fast.Next != nil && fast.Next.Next != nil {
// 快慢指针
slow = slow.Next
fast = fast.Next.Next
if slow == fast {
// 链表有环
// 快指针走的路程:
// 慢指针走的路程:
// 快指针是慢指针走的路程的2倍
fast = head
for slow != fast {
slow = slow.Next
fast = fast.Next
}
return slow
}
}
return result
}
问题1: 如何判断链表是否有环
慢指针每次走一个链表元素,快指针每次走两个链表元素,如果链表有环,快指针一定会追上慢指针,也就是slow == fast
问题2:如何找到环入口
假设从头结点到环形入口节点 的节点数为x。 环形入口节点到 fast指针与slow指针相遇节点 节点数为y。 从相遇节点 再到环形入口节点节点数为 z。 如图所示:
当slow和fast相遇:
slow走过的路程:x+y
fast走过的路程:x+y+(y+z)*n
这里的slow为什么不是x+y+(y+z)*n, 也就是为什么slow在进入环形入口第一圈的时候就被追上了?
fast指针比slow指针早进入环,假设在slow指针指向环形入口节点的时候,环形入口节点(slow)到fast指针的节点数为p,如上图,一圈长度为y+z,(p<y+z,因为当p=y+z,slow和fast指针直接相遇),当slow走过一圈时,
slow走过的路程:y+z
fast走过的路程:2*(y+z)
fast比slow多走了一圈y+z,所以在slow第一圈的时候,slow和fast一定相遇
fast的路程是slow路程的2倍
(x+y)*2 = x+y+(y+z)*n
x+y= (y+z)*n
x = z + (y+z)*(n-1)
当n=1时,x==z,n>1时,x==z + n-1圈,这样就可以定于两个指针
index1 指向头节点,index2指向fast和slow相遇的节点,index1和index2每一次走一个节点,相遇的节点就是环形入口节点。
n>1时,index2会多走n-1圈,和index1相遇,相遇的节点就是环形入口节点
总结
如何使用快慢指针
如何使用虚拟头节点
在链表:听说用虚拟头节点会方便很多? (opens new window)中,我们讲解了链表操作中一个非常重要的技巧:虚拟头节点。
链表的一大问题就是操作当前节点必须要找前一个节点才能操作。这就造成了,头结点的尴尬,因为头结点没有前一个节点了。
每次对应头结点的情况都要单独处理,所以使用虚拟头结点的技巧,就可以解决这个问题。
在链表:听说用虚拟头节点会方便很多? (opens new window)中,我给出了用虚拟头结点和没用虚拟头结点的代码,大家对比一下就会发现,使用虚拟头结点的好处。