算法5 --- 链表(golang)

111 阅读11分钟

请判断一个链表是否为回文链表

示例812565218 将后半部分逆序得到81256 再与前半部分对比看是否相等

package main

import "fmt"

//***如字符串是通过单链表来存储的,那该如何来判断是一个回文串呢?(如level,noon)***//

// 那么单链表怎么存储字符串?
type StrNode struct {
   str  string
   next *StrNode //这个表示指向下一个结点
}

// 给链表插入一个结点,在单链表的最后加入
func InsertStrNode(head *StrNode, newStrNode *StrNode) {
   //1.先找到最后一个结点
   //2.创建一个辅助结点
   temp := head
   for {
      //3.如果temp的next指向空,那就是最后一个结点
      if temp.next == nil {
         break
      }
      temp = temp.next //4.让temp不断指向下一个结点
   }
   //5.将newStrNode加入到链表的最后
   temp.next = newStrNode
   //fmt.Println("这就连上了")
}

// 显示链表的所有结点信息
func ListStrNode(head *StrNode) {

   //1.创建一个辅助结点
   temp := head
   //2.判度该链表是不是空
   if temp == nil || temp.next == nil {
      fmt.Println("空链表,无法显示")
      return
   }

   //3.遍历这个链表
   for {
      fmt.Printf("[%s]=>", temp.str)
      //4.判断结点是否到链表结尾
      if temp.next == nil {
         fmt.Println("")
         break
      }
      temp = temp.next //5.指向下一个结点

   }
}

// 找到链表中间节点,将后半部分转置,再从左向右遍历对比
func IsPalindrome(head *StrNode) bool {
   if head == nil || head.next == nil {
      return true
   }
   res := true
   n1 := head
   n2 := head
   //快慢指针 快指针走两步 慢指针走一步,当快指针走完,慢指针刚好走到中点(偶数个走到中点左边的位置)
   for n1.next != nil && n2.next != nil && n2.next.next != nil {
      n1 = n1.next
      n2 = n2.next.next
   }

   //将后半部分逆序
   n2 = n1.next
   n1.next = nil
   //先将5>6给n1  2>1>8给回n2
   //然后2>5>5给n1 1>8 给回n2
   //直到给完n2
   for n2 != nil {
      n3 := n2.next
      n2.next = n1
      n1 = n2
      n2 = n3
   }
   n3 := n1
   n2 = head

   //将逆序部分与前半部分比对
   for n1 != nil && n2 != nil {
      if n1.str != n2.str {
         res = false
         break
      }
      n1 = n1.next
      n2 = n2.next
   }

   //将后半部分还原
   //n3 =[8]=>[1]=>[2]=>[5]=>[6]=>
   n1 = n3.next
   //n1= [1]=>[2]=>[5]=>[6]=>
   n3.next = nil
   for n1 != nil {
      n2 = n1.next
      n1.next = n3
      n3 = n1
      n1 = n2
   }
   return res
}

func main() {

   //a := 812565218

   //1.先创建一个头结点
   head := &StrNode{
      str: "8",
      //这里没法定义next,next要指向谁,要通过下一个结点的主动加入链表来确定,这里先空着
   }

   //2.创建新的StrNode
   str1 := &StrNode{
      str: "1",
   }
   str2 := &StrNode{
      str: "2",
   }
   str3 := &StrNode{
      str: "5",
   }
   str4 := &StrNode{
      str: "6",
   }
   str5 := &StrNode{
      str: "5",
   }
   str6 := &StrNode{
      str: "2",
   }
   str7 := &StrNode{
      str: "1",
   }
   str8 := &StrNode{
      str: "8",
   }

   //3.加入
   InsertStrNode(head, str1)
   InsertStrNode(head, str2)
   InsertStrNode(head, str3)
   InsertStrNode(head, str4)
   InsertStrNode(head, str5)
   InsertStrNode(head, str6)
   InsertStrNode(head, str7)
   InsertStrNode(head, str8)

   //4.显示
   ListStrNode(head)

   //5.判断
   res := IsPalindrome(head)
   fmt.Println(res)
}

将单向链表按某值划分成左边小、中间相等、右边大的形式

简单做法:将链表每个节点遍历成list数组,再用快排讲过的partition分成左中右三块,再把数组串起来成链表

空间复杂度小的做法:

如4 6 3 5 8 5 2 ,按5 划分

设置六个变量:左边区域的头和尾,中间区域的头和尾,右边区域的头和尾,再将左边中间右边的头尾相连

package main

import "fmt"

type Node struct {
   value int
   next  *Node //这个表示指向下一个结点
}

// 给链表插入一个结点,在单链表的最后加入
func InsertStrNode(head *Node, newNode *Node) {
   //1.先找到最后一个结点
   //2.创建一个辅助结点
   temp := head
   for {
      //3.如果temp的next指向空,那就是最后一个结点
      if temp.next == nil {
         break
      }
      temp = temp.next //4.让temp不断指向下一个结点
   }
   //5.将newNode加入到链表的最后
   temp.next = newNode
   //fmt.Println("这就连上了")
}

// 显示链表的所有结点信息
func ListNode(head *Node) {

   //1.创建一个辅助结点
   temp := head
   //2.判度该链表是不是空
   if temp == nil {
      fmt.Println("空链表,无法显示")
      return
   }

   //3.遍历这个链表
   for {
      fmt.Printf("[%d]=>", temp.value)
      //4.判断结点是否到链表结尾
      if temp.next == nil {
         fmt.Println("")
         break
      }
      temp = temp.next //5.指向下一个结点

   }
}

func listPartition2(head *Node, pivot int) *Node {
   var sh, st, eh, et, mh, mt, next *Node
   for head != nil {
      next = head.next
      head.next = nil
      if head.value < pivot {
         if sh == nil {
            sh = head
            st = head
         } else {
            st.next = head //注意这里的sh就变成了head[0]->head[1]
            st = head
         }
      } else if head.value == pivot {
         if eh == nil {
            eh = head
            et = head
         } else {
            et.next = head
            et = head
         }

      } else {
         if mh == nil {
            mh = head
            mt = head
         } else {
            mt.next = head
            mt = head
         }
      }
      head = next
   }
   if st != nil { //如果有小于区域
      st.next = eh   //将小于尾和等于头连起来
      if et == nil { //如果没有等于区域
         et = st
      }
   }
   if et != nil {
      et.next = mh //等于尾或者小于尾 连上大于头
   }

   //有小于就返回小于头
   //没有小于返回等于头
   //没有等于返回大于头
   if sh != nil {
      return sh
   } else {
      if eh != nil {
         return eh
      } else {
         return mh
      }
   }

}
func main() {

   //a := 812565218

   //1.先创建一个头结点
   head := &Node{
      value: 8,
      //这里没法定义next,next要指向谁,要通过下一个结点的主动加入链表来确定,这里先空着
   }

   //2.创建新的Node
   n1 := &Node{
      value: 1,
   }
   n2 := &Node{
      value: 2,
   }
   n3 := &Node{
      value: 5,
   }
   n4 := &Node{
      value: 6,
   }
   n5 := &Node{
      value: 5,
   }
   n6 := &Node{
      value: 2,
   }
   n7 := &Node{
      value: 1,
   }
   n8 := &Node{
      value: 8,
   }

   //3.加入
   InsertStrNode(head, n1)
   InsertStrNode(head, n2)
   InsertStrNode(head, n3)
   InsertStrNode(head, n4)
   InsertStrNode(head, n5)
   InsertStrNode(head, n6)
   InsertStrNode(head, n7)
   InsertStrNode(head, n8)

   //4.显示
   ListNode(head)

   //5.判断
   res := listPartition2(head, 5)
   ListNode(res)
}

复制含有随机指针的链表

一种特殊的单链表节点如下

type Node struct {
   value int
   next  *Node //这个表示指向下一个结点
   rand  *Node //指向随机节点
}

给定一个由Node节点类型组成的无环单链表的头结点head,请实现一个函数完成这个链表的复制,并返回复制的新链表的头节点。 【要求】时间复杂度O(n),额外空间复杂度O(1)

解1:利用map[node,node],遍历链表得到,key(old1) value (new1),key(old2) value(new2) ...map<old,new>

old1 的next是old2 ,那么new2的next是什么呢,是不是通过map[old2]找到,所以newN=map[oldN]

newN.next --> map[oldN].next=map[oldN.next]

newN.rand --> map[oldN].rand=map[oldN.rand]

解2:将1>2>3,转成1>1'>2>2'>3>3',如果1的rand是3,那么1'的rand就是3',等效于1的rand的next 代码实现

package main

import (
   "fmt"
)

type Node struct {
   value int
   next  *Node //这个表示指向下一个结点
   rand  *Node
}


// 显示链表的所有结点信息
func ListNode(head *Node) {

   //1.创建一个辅助结点
   temp := head
   //2.判度该链表是不是空
   if temp == nil {
      fmt.Println("空链表,无法显示")
      return
   }

   //3.遍历这个链表
   for {
      fmt.Printf("[%d]=>", temp.value)
      //4.判断结点是否到链表结尾
      if temp.next == nil {
         fmt.Println("")
         break
      }
      temp = temp.next //5.指向下一个结点

   }
}

func copyListWithRand1(head *Node) *Node {
   mapNode := make(map[*Node]*Node, 0)
   cur := head
   for cur != nil {
      //这里只给了value,新copy的next和rand都为空
      mapNode[cur] = &Node{value: cur.value}
      cur = cur.next
   }
   cur = head
   for cur != nil {
      mapNode[cur].next = mapNode[cur.next]
      mapNode[cur].rand = mapNode[cur.rand]
      cur = cur.next
   }
   return mapNode[head]
}

func copyListWithRand2(head *Node) *Node {
   cur := head
   var next *Node
   //1>2>3
   for cur != nil {
      next = cur.next
      cur.next = &Node{value: cur.value}
      cur.next.next = next
      cur = next
   }
   //1>1'>2>2'>3>3'
   ListNode(head)
   cur = head
   var copy *Node
   for cur != nil {
      next = cur.next.next
      copy = cur.next
      if cur.rand != nil {
         copy.rand = cur.rand.next //比如1的rand是3  那么1'的rand就是3' 等效于3的next
      }
      cur = next
   }
   //split
   cur = head
   res := head.next //1>1'>2>2'>3>3' 拿到节点1'
   for cur != nil {
      next = cur.next.next //2>2'>3>3'
      copy = cur.next      //copy 1'>2>2'>3>3'
      cur.next = next      //1>2>2'>3>3'
      if next != nil {
         copy.next = next.next //1'>2'>3>3'
      }
      cur = next //2>2'>3>3'
   }

   return res
}

func main() {

   //a := 812565218

   //1.先创建一个头结点
   head := &Node{
      value: 0,
      //这里没法定义next,next要指向谁,要通过下一个结点的主动加入链表来确定,这里先空着
   }

   //2.创建新的Node
   n1 := &Node{
      value: 1,
   }
   n2 := &Node{
      value: 2,
   }
   n3 := &Node{
      value: 3,
   }

   //3.加入
   head.next = n1
   n1.next = n2
   n2.next = n3

   head.rand = n3
   n1.rand = head
   n3.rand = n2

   //res := copyListWithRand1(head)
   res := copyListWithRand2(head)
   ListNode(res)
}

请找到单链表第一个入环节点

    graph TB

    A((a))
    B((b))
    C((c))
    D((d))
    E((e))

    A-->B
    B-->C
    C-->D
    D-->E
    E-->C

图中c就是第一个入环节点

解1:从a开始next遍历,判断a存在与否,不存在放进map。判断b存在与否,不存在放进map。判断c存在与否,不存在放进map,判断d存在与否,不存在放进map。判断e存在与否,不存在放进map,判断c存在与否,存在返回c

解2:不用map减少空间复杂度的做法,使用快慢指针。快指针一次走两步,慢指针一次走一步,如果快指针走到nil,说明没有环(有环就一定走不到结尾)。如果快指针和慢指针相遇,让快指针返回开头,快慢指针调整成每次走一步。快指针和慢指针相遇的地方就是第一个入环节点。(该方法比较魔性,建议记住就行)

代码实现

package main

import (
   "fmt"
)

type Node struct {
   value int
   next  *Node //这个表示指向下一个结点
   rand  *Node
}

// 显示链表的所有结点信息
func ListNode(head *Node) {

   //1.创建一个辅助结点
   temp := head
   //2.判度该链表是不是空
   if temp == nil {
      fmt.Println("空链表,无法显示")
      return
   }

   //3.遍历这个链表
   for {
      fmt.Printf("[%d]=>", temp.value)
      //4.判断结点是否到链表结尾
      if temp.next == nil {
         fmt.Println("")
         break
      }
      temp = temp.next //5.指向下一个结点

   }
}

func firstLoop(head *Node) *Node {
   if head == nil || head.next == nil || head.next.next == nil {
      return nil
   }
   slow := head.next
   fast := head.next.next
   //快指针一次两步 慢指针一次一步
   for slow != fast {
      if fast.next == nil || fast.next.next == nil {
         return nil
      }
      slow = slow.next
      fast = fast.next.next
   }
   //快指针返回开头 ,快慢指针都一次一步
   fast = head
   for slow != fast {
      slow = slow.next
      fast = fast.next
   }
   return slow
}

func main() {

   //a := 812565218

   //1.先创建一个头结点
   head := &Node{
      value: 0,
      //这里没法定义next,next要指向谁,要通过下一个结点的主动加入链表来确定,这里先空着
   }

   //2.创建新的Node
   n1 := &Node{
      value: 1,
   }
   n2 := &Node{
      value: 2,
   }
   n3 := &Node{
      value: 3,
   }
   n4 := &Node{
      value: 4,
   }
   n5 := &Node{
      value: 5,
   }
   //3.加入
   head.next = n1
   n1.next = n2
   n2.next = n3
   n3.next = n4
   n4.next = n5
   n5.next = n3

   fmt.Println(firstLoop(head))
}

请找到两个单链第一个相交的节点

思路:按照是否有无环区分

  1. 如果都无环,它们只能是这种形态
    graph TB
    
    A((a))
    B((b))
    C((c))
    D((d))
    E((e))
    
    A1((a1))
    
    A-->B
    B-->C
    C-->D
    D-->E
    
    A1-->C
    

我们遍历a 和 a1 ,获得长度l 和 l1,和结尾节点e 和e1,如果e和e1不相等,则不存在相交

如果相等,a先走步数l1-l,然后a和a1一起遍历,第一个相等的节点就是第一个相交的节点

  1. 如果一个有环,一个无环,则不存在相交(你可以画画图,是不存在这种相交情况的)

  2. 如果都有环又分为三种情况

  • 情况一:

    graph TB

    A((a))
    B((b))
    C((c))
    D((d))
    E((e))

    A1((a1))
    B1((b1))
    C1((c1))
    D1((d1))
    E1((e1))
    
    A-->B
    B-->C
    C-->D
    D-->E
    E-->C

    A1-->B1
    B1-->C1
    C1-->D1
    D1-->E1
    E1-->C1

这种第一个回环节点c不等于c1,将它走一遍loop,如果都找不到等于c1的节点,就返回空,不相交

  • 情况二

    graph TB

    A((a))
    B((b))
    C((c))
    D((d))
    E((e))
    F((f))
    A1((a1))
    
    A-->B
    B-->C
    C-->D
    D-->E
    E-->F
    F-->D
    A1-->C

这种第一个回环节点d=d1,将d当结尾节点,等效于情况一

  • 情况三

    graph TB

    A((a))
    B((b))
    C((c))
    D((d))
    E((e))
    F((f))
    A1((a1))
    B2((b2))
    C2((c2))
    
    A1-->B2
    B2-->C2
    C2-->F
    A-->B
    B-->C
    C-->D
    D-->E
    E-->F
    F-->C

这里的c和f都可以当做第一个相交节点

将a1的第一个回环节点f,再走一圈,如果找到另一个链表的回文节点c,则f或c就是第一个相交节点

代码实现

package main

import (
   "fmt"
)

type Node struct {
   value int
   next  *Node //这个表示指向下一个结点
   rand  *Node
}

// 显示链表的所有结点信息
func ListNode(head *Node) {

   //1.创建一个辅助结点
   temp := head
   //2.判度该链表是不是空
   if temp == nil {
      fmt.Println("空链表,无法显示")
      return
   }

   //3.遍历这个链表
   for {
      fmt.Printf("[%d]=>", temp.value)
      //4.判断结点是否到链表结尾
      if temp.next == nil {
         fmt.Println("")
         break
      }
      temp = temp.next //5.指向下一个结点

   }
}

func firstLoop(head *Node) *Node {
   if head == nil || head.next == nil || head.next.next == nil {
      return nil
   }
   slow := head.next
   fast := head.next.next
   //快指针一次两步 慢指针一次一步
   for slow != fast {
      if fast.next == nil || fast.next.next == nil {
         return nil
      }
      slow = slow.next
      fast = fast.next.next
   }
   //快指针返回开头 ,快慢指针都一次一步
   fast = head
   for slow != fast {
      slow = slow.next
      fast = fast.next
   }
   return slow
}

func noLoop(head *Node, head1 *Node) *Node {
   cur, cur1, diff := head, head1, 0
   for cur.next != nil {
      diff++
      cur = cur.next
   }
   diff++ //算上尾节点
   for cur1.next != nil {
      diff--
      cur1 = cur1.next
   }
   diff-- //算上尾节点
   //两个无环单链的结尾值不一样,就是不相交
   if cur != cur1 {
      return nil
   }
   //长的给cur
   if diff > 0 {
      cur = head
      cur1 = head1
   } else {
      cur = head1
      cur1 = head
      diff = -diff
   }
   //长的走diff步
   for diff != 0 {
      cur = cur.next
      diff--
   }

   //找到第一个相交的节点

   for cur1 != cur {
      cur = cur.next
      cur1 = cur1.next
   }
   return cur
}

func bothloop(head1, loop1, head2, loop2 *Node) *Node {
   var cur1 *Node
   var cur2 *Node
   //情况二 将loop1当做结尾节点求两个无环相交
   if loop1 == loop2 {
      cur1 = head1
      cur2 = head2
      diff := 0
      for cur1.next != loop1 {
         diff++
         cur1 = cur1.next
      }
      diff++ //算上尾节点
      for cur2.next != loop2 {
         diff--
         cur2 = cur2.next
      }
      diff-- //算上尾节点
      //长的给cur1
      if diff > 0 {
         cur1 = head1
         cur2 = head2
      } else {
         cur1 = head2
         cur2 = head1
         diff = -diff
      }
      //长的先走diff步
      for diff != 0 {
         diff--
         cur1 = cur1.next
      }
      for cur1 != cur2 {
         cur1 = cur1.next
         cur2 = cur2.next
      }
      return cur1
   } else {
      cur1 = loop1.next
      for cur1 != loop1 {
         if cur1 == loop2 {
            return loop1
         }
         cur1 = cur1.next
      }
      return nil
   }
}

func getIntersectNode(head1, head2 *Node) *Node {
   if head1 == nil || head2 == nil {
      return nil
   }
   loop1 := firstLoop(head1)
   loop2 := firstLoop(head2)
   if loop1 == nil && loop2 == nil {
      return noLoop(head1, head2)
   }
   if loop1 != nil && loop2 != nil {
      return bothloop(head1, loop1, head2, loop2)
   }
   //一个有环一个无环的直接返回不存在
   return nil
}

func main() {

   //a := 812565218

   //1.先创建一个头结点
   head1 := &Node{
      value: 0,
      //这里没法定义next,next要指向谁,要通过下一个结点的主动加入链表来确定,这里先空着
   }

   //2.创建新的Node
   n1 := &Node{
      value: 1,
   }
   n2 := &Node{
      value: 2,
   }
   n3 := &Node{
      value: 3,
   }
   n4 := &Node{
      value: 4,
   }
   n5 := &Node{
      value: 5,
   }
   n6 := &Node{
      value: 6,
   }
   n7 := &Node{
      value: 7,
   }
   n8 := &Node{
      value: 8,
   }
   n9 := &Node{
      value: 9,
   }

   head2 := &Node{
      value: 0,
      //这里没法定义next,next要指向谁,要通过下一个结点的主动加入链表来确定,这里先空着
   }
   //2.创建新的Node
   k1 := &Node{
      value: 1,
   }
   k2 := &Node{
      value: 2,
   }
   k3 := &Node{
      value: 3,
   }
   k4 := &Node{
      value: 4,
   }
   //k3 := &Node{
   // value: 3,
   //}
   //k4 := &Node{
   // value: 4,
   //}
   //k5 := &Node{
   // value: 5,
   //}
   //3.加入
   head1.next = n1
   n1.next = n2
   n2.next = n3
   n3.next = n4
   n4.next = n5
   n5.next = n6
   n6.next = n7
   n7.next = n8
   n8.next = n9
   n9.next = n7

   head2.next = k1
   k1.next = k2
   k2.next = k3
   k3.next = k4
   k4.next = n3

   fmt.Println(getIntersectNode(head1, head2))
}