Day04 链表-02

72 阅读4分钟

概念

刷题

24 两辆交换链表中的节点

leetcode.cn/problems/sw…

image.png

使用虚拟头节点,解题思路:

19 删除链表的倒数第N个节点

leetcode.cn/problems/re…

image.png

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 链表相交

leetcode.cn/problems/in…

image.png

解题思路:

判断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

leetcode.cn/problems/li…

使用hash map来记录每一次遍历的元素,代码简单,但是hash map需要消耗内存,每一次加入新的元素需要进行一次hash查找。

图片.png

使用快慢指针

image.png

代码:

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。 如图所示:

image.png

当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)中,我给出了用虚拟头结点和没用虚拟头结点的代码,大家对比一下就会发现,使用虚拟头结点的好处。