day04 两两交换链表中的节点、删除链表倒数第N个节点、链表相交、环形链表II

题目链接:leetcode24-两两交换链表中的节点, leetcode19-删除链表倒数第N个节点, leetcode160-链表相交, leetcode142-环形链表II

两两交换链表中的节点

基础题没有什么技巧

解题思路见代码注释
时间复杂度: O(n)
空间复杂度: O(1)

Go

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

	virtualHead := &ListNode{Next: head}

	// 指针
	preNode, curNode := virtualHead, head
	var curNodeNext *ListNode

	for curNode != nil && curNode.Next != nil {
		curNodeNext = curNode.Next

		// 初始:cur指向1,curNext指向2
		// prevNode ---> 1----> 2 -------> 3
		//               cur   curNext
		// curNode与curNodeNext进行节点交换

		// prevNode ---> 1    2 -------> 3
		//               |_______________^
		//              cur   curNext
		curNode.Next = curNodeNext.Next

		//                _______
		//               V      |
		// prevNode ---> 1      2        3
		//               |_______________^
		//              cur    curNext
		curNodeNext.Next = curNode

		// prevNode ---> 2 -----> 1 -----> 3
		//              curNext  cur
		preNode.Next = curNodeNext

		// 指针移动
		// 2 -----> 1 -----> 3
		//          preNode  cur
		preNode = curNode
		curNode = curNode.Next

	}
	return virtualHead.Next
}

删除链表倒数第N个节点

思路

节点数范围:1 <= sz <= 30
n范围: 1 <= n <= sz (即n一定时小于节点的数量)
思路:使用快慢指针,slow, fast

  1. 创建一个dummy head
  2. 从dummy开始, 初始slow,fast都指向dummy, fast先移动n个节点
  3. slow和fast同时移动,当fast.Next==nil时,slow所处位置即为倒数第n个节点的前一个节点

example:

dummyH ---> 1 ----> 2 ----> 3 ---> 4

假如n=2, 则要删除倒数第2个节点,即节点3

	初始s和f都指向dummyH:
		dummyH ---> 1 ----> 2 ----> 3 ---> 4
		s  f
	f先移动n个节点:
		dummyH ---> 1 ----> 2 ----> 3 ---> 4
		 s                  f
	s和f同时移动:
	dummyH ---> 1 ----> 2 ----> 3 ---> 4
			            s              f

即当f.Next == nil时,s刚好处于倒数第n个节点的前一个节点,此时只需要执行s.Next = s.Next.Next,就删除了目标节点

时间复杂度: O(n)
空间复杂度: O(1)

Go

func removeNthFromEnd(head *ListNode, n int) *ListNode {
   if head == nil {
   	return head
   }

   dummyH := &ListNode{Next: head}

   s, f := dummyH, dummyH

   // f先移动n个节点
   for i := 0; i < n; i++ {
   	f = f.Next
   }

   for f.Next != nil {
   	s = s.Next
   	f = f.Next
   }

   // 此时s所处的位置为倒数第n个节点的前一个节点
   s.Next = s.Next.Next
   return dummyH.Next
}

链表相交

思路

方法1:
 使用map将A链表节点存储,遍历B链表时判断当前节点是否已经在map中存在,如果存在当前节点就是相交节点.
 此方法时间复杂度O(m+n),m和n分别是链表A,B的长度, 空间复杂度O(m)
方法2:
 将A B补成等长的链表,如下:
 A: a1—>a2—>c1—>c2—>c3—>b1—>b2—>b3—>c1—>c2—>c3
 B: b1—>b2—>b3—>c1—>c2—>c3—>a1—>a2—>c1—>c2—>c3
可以看到,是在c1处相交
 原理:
  假设A链表中,不相交的节点数量有a,相交的节点数量有c,总长度为a+c
  假设B链表中,不相交的节点数量有b,相交的节点数量有c,总长度为b+c
  使用pa, pb两指针同时遍历A链表和B链表
  假如a==b, 遍历链表的两指针会同时到达相交的节点
  假如a!=b,pa遍历A链表之后遍历B链表,pb遍历B链表之后遍历A链表
   此时对于A链表来说总长度为: (a+c) + (b+c) —> (a+c+b) + c
   对于B链表来说总长度为: (b+c) + (a+c) —> (b+c+a) + c
   即,pa和pb都遍历了(a+b+c)个节点然后同时到达了相交的节点

时间复杂度O(m+n),m和n分别是链表A,B的长度
空间复杂度O(1)

Go

func getIntersectionNode(headA, headB *ListNode) *ListNode {
	// 两个链表都不为nil时才会相交
	if headA == nil || headB == nil {
		return nil
	}

	pa, pb := headA, headB

	// 如果A和B没有相交的点,最终pa和pb都会为nil, 然后退出
	for pa != pb {
		if pa == nil {
			// A已经遍历完,开始遍历B
			pa = headB
		} else {
			pa = pa.Next
		}

		if pb == nil {
			// B已经遍历完开始遍历A
			pb = headA
		} else {
			pb = pb.Next
		}
	}

	return pa
}

环形链表II

思路

  1. 判断链表是否有环
  2. 找出环的入口(返回入口节点所在的索引,索引从0开始)

推导过程

初始状态:
day04 两两交换链表中的节点、删除链表倒数第N个节点、链表相交、环形链表II_第1张图片
存在双指针,slow、fast初始都指向链表的表头,slow的步长为1,fast的步长为2。
如果链表存在环,则slow与fast 一定会在环内相交 ,因为fast相对于slow的步长为1,即fast在一步一步的接近slow。
则有:
day04 两两交换链表中的节点、删除链表倒数第N个节点、链表相交、环形链表II_第2张图片
怎么求环的入口?
 设链表head到环入口的距离为x, 环入口到slow和fast相交点的距离为y,相交点到环入口的距离为z,则有:
day04 两两交换链表中的节点、删除链表倒数第N个节点、链表相交、环形链表II_第3张图片
slow和fast同时从链表头开始移动到相交分别移以下距离:
slow:x+y
fast: x+y+n(y+z)
(fast与slow相交之前可能已经在环中转了n圈,一圈距离为y+z)

因为fast的步长为slow的2倍则有:

2(x+y)= x+y+n(y+z)
--> x+y = n(y+z)
--> x = n(y+z)-y
--> x = (n-1)(y+z) + z

x = (n-1)(y+z) + z可以得知,x的长度等距离z加上n-1圈的环的距离((y+z)为一圈环的距离,且(n-1) >= 0)则有:

假如存在两个指针p1, p2, p1指向链表头部向着环入口出发,p2指向slow与fast的相交点向着环入口出发,p1和p2同时开始移动,
则p1与p2的相交点即为环入口。 (即p2从相交点移动到环入口(距离z)后可能会转(n-1)圈环)
day04 两两交换链表中的节点、删除链表倒数第N个节点、链表相交、环形链表II_第4张图片

Go

func detectCycle(head *ListNode) *ListNode {
	slow, fast := head, head

	// 如果存在环,fast.Next不可能为nil
	for fast != nil && fast.Next != nil {
		// slow的步长为1
		// fast的步长为2
		slow = slow.Next
		fast = fast.Next.Next

		// 当slow与fast相等时,说明在环内相交了
		if slow == fast {
			// 找出环的入口
			p1 := head
			p2 := slow
			// p1从链表头部开始移动,p2从相交点开始移动,两者相交的点即为环的入口
			for p1 != p2 {
				p1, p2 = p1.Next, p2.Next
			}

			return p1
		}
	}

	return nil
}

你可能感兴趣的:(算法,算法)