常见算法之链表

71 阅读5分钟

基础知识

链表是一种常见的基础数据结构。在链表中,每个节点包含指向下一个节点的指针,这些指针把节点连接成链状结构。在创建链表时无须事先知道链表的长度。链表节点的内存分配不是在创建链表时一次性地完成,而是每添加一个节点分配一次内存。当插入一个节点时,只需要为新节点分配内存,然后通过调整指针的指向来确保新节点被链接到链表中。这样,链表就能实现灵活的内存动态管理,可以充分地利用计算机的内存资源。和数组相比,链表更适合用来存储一个大小动态变化的数据集。如果需要在一个数据集中频繁地添加新的数据并且不需要考虑数据的顺序,那么可以用链表来实现这个数据集。链表中的插入操作可以用O(1)的时间来实现。由于链表中的内存不是一次性分配的,因此链表节点在内存中的地址并不是连续的。如果想在链表中找到链表的第i个节点,就只能从头节点开始朝着指向下一个节点的指针遍历链表,它的时间效率为O(n)。而在数组中,根据下标用O(1)的时间就能找到第i个元素。

常见算法

一、链表中环的入口节点

题目:如果一个链表中包含环,那么应该如何找出环的入口节点?从链表的头节点开始顺着next指针方向进入环的第1个节点为环的入口节点。

image.png

解题思路:

  1. 通过快慢指针确定该链表是否有环,快指针每次前进两步,慢指针每次前进一步,两个指针相遇就说明有环。
  2. 将慢指针绕环一圈记录环的长度k。
  3. 然后从头指针开始,先让快指针走K步,然后两个同时往前走,直到相遇就是入口节点。

Golang代码

func detectCycle(head *ListNode) *ListNode {
   var (
      prev = head
      back = head
   )

   if head == nil {
      return nil
   }

   for {

      if prev.Next != nil {
         prev = prev.Next
      }

      if back.Next != nil {
         back = back.Next
      }

      if back.Next != nil {
         back = back.Next
      }

      if prev == back {
         break
      }
   }

   if prev.Next == nil {
      return nil
   }

   var k int

   for {
      prev = prev.Next
      k++
      if prev == back {
         break
      }
   }

   prev = head
   back = head

   for i := 0; i < k; i++ {
      back = back.Next
   }

   for {
      if prev == back {
         return prev
      }

      prev = prev.Next
      back = back.Next
   }

}

二、两个链表的第1个重合节点

题目:输入两个单向链表,请问如何找出它们的第1个重合节点。例如,图中的两个链表的第1个重合节点的值是4。

image.png

解题思路:

三种解法

  1. 将尾节点的下个节点设置为任意一个链表的头,然后寻找链表的环形入口节点
  2. 将两个队列节点分别压入两个栈内,然后分别出栈对比,直到找到不相等的节点,同时就找到了重合点。
  3. 将两个队列的长度遍历出来,然后减一个差值,先用长链表向前移动这个差值,然后两个链表同时向前移动对比,直到找到相等的节点,就是重合点

Golang代码:

func getIntersectionNode(headA, headB *ListNode) *ListNode {

   var (
      left = headA
      right = headB
      la int
      lb int
      diff int
   )

   for {
      if left.Next == nil && right.Next == nil {
         break
      }

      if left.Next != nil {
         left = left.Next
         la++
      }

      if right.Next != nil {
         right = right.Next
         lb++
      }
   }

   left = headA
   right = headB

   if la > lb {
      diff = la - lb
      for i := 0; i < diff; i++ {
         if left.Next != nil {
            left = left.Next
         }
      }
   } else {
      diff = lb - la
      for i := 0; i < diff; i++ {
         if right.Next != nil {
            right = right.Next
         }
      }
   }

   for {
      if left == right {
         return left
      }

      if left.Next == nil && right.Next == nil {
         return nil
      }

      left = left.Next
      right = right.Next
   }

}

三、链表中的数字相加

题目:给定两个表示非负整数的单向链表,请问如何实现这两个整数的相加并且把它们的和仍然用单向链表表示?链表中的每个节点表示整数十进制的一位,并且头节点对应整数的最高位数而尾节点对应整数的个位数。

image.png

解题思路:

  • 分别翻转两个链表
  • 然后一个节点一个节点对应相加,如果有进位就放到下个节点一起加,获取一条新的链
  • 翻转新的链,就是结果

Golang代码:

func addTwoNumbers(l1 *ListNode, l2 *ListNode) *ListNode {
   r1 := ReverseLinked(l1)
   r2 := ReverseLinked(l2)
   r3 := &ListNode{}
   var ten int
   dealAdd(r3, r1, r2, ten)
   return ReverseLinked(r3.Next)
}

func dealAdd(r3 *ListNode, r1 *ListNode, r2 *ListNode, ten int) {
   var res int

   if r1 == nil && r2 == nil && ten == 0 {
      return
   }

   if r1 != nil {
      res += r1.Val
      r1 = r1.Next
   }

   if r2 != nil {
      res += r2.Val
      r2 = r2.Next
   }

   res += ten

   if res > 9 {
      ten = 1
      res -= 10
   } else {
      ten = 0
   }

   r3.Next = &ListNode{
      Val: res,
   }
   r3 = r3.Next
   dealAdd(r3, r1, r2, ten)
}


func ReverseLinked(head *ListNode) *ListNode {

   if  head == nil {
      return nil
   }

   var rev *ListNode
   for head != nil {
      hold := head
      head = head.Next

      if rev == nil {
         rev = hold
         rev.Next = nil
      } else {
         hold.Next = rev
         rev = hold
      }
   }

   return rev
}

四、重排链表

问题:给定一个链表,链表中节点的顺序是L0→L1→L2→…→Ln-1→Ln,请问如何重排链表使节点的顺序变成L0→Ln→L1→Ln-1→L2→Ln-2→…?

image.png

解题思路:

  • 通过快慢指针,快指针每次移动两个节点,慢指针每次移动一个节点,等快节点移动到尾部,慢指针移动到了中间部分
  • 将前半部分和后半部分分离
  • 后半部分翻转
  • 将两个链表交替合并到一个链表中

Golang代码:

func reorderList(head *ListNode)  {

   hLen := getLen(head)

   if hLen == 0 || hLen == 1 {
      return
   }

   var half int
   left := head
   var right *ListNode
   if hLen%2 == 1 {
      half = hLen/2 + 1
   } else {
      half = hLen/2
   }

   half--
   for half > 0 {
      left = left.Next
      half--
   }

   right = left.Next
   left.Next = nil
   right = ReverseLinked(right)
   left = head
   prev := &ListNode{}
   var tmp *ListNode

   for left != nil || right != nil {
      if left != nil {
         tmp = left
         left = left.Next
         tmp.Next = nil
         prev.Next = tmp
         prev = tmp
      }

      if right != nil {
         tmp = right
         right = right.Next
         tmp.Next = nil
         prev.Next = tmp
         prev = tmp
      }
   }
}

func getLen(head *ListNode) int {
   var res int
   for head != nil {
      head = head.Next
      res++
   }
   return res
}

func ReverseLinked(head *ListNode) *ListNode {

   if  head == nil {
      return nil
   }

   var rev *ListNode
   for head != nil {
      hold := head
      head = head.Next

      if rev == nil {
         rev = hold
         rev.Next = nil
      } else {
         hold.Next = rev
         rev = hold
      }
   }

   return rev
}