力扣刷题第十天--链表篇

前言

今天基本上一天都花在了链表上,用两种方法重写了昨天的设计链表,并写了如下的两道题,还得完善,时间原因只能留到明天了。

内容

一、环形链表

141.环形链表

给你一个链表的头节点 head ,判断链表中是否有环。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。

如果链表中存在环 ,则返回 true 。 否则,返回 false 。

哈希表

首先,pos不作为参数传递,(不然就简单了)想到哈希表,存储访问过的结点,再遇到时,说明有环。否则就将该节点加入哈希表中。重复这一过程,直到我们遍历完整个链表即可。

  //4ms 5.75MB  哈希表空间换时间,所以内存消耗还是挺高的

func hasCycle(head *ListNode) bool {
  
  hashTable:=map[*ListNode]struct{}{}

  for head!=nil{
    if _,ok:=hashTable[head];ok{//; := ==
      return true
    }
    hashTable[head]=struct{}{}//这行代码将空结构体 struct{} 存储到 hashTable[head] 中。由于结构体类型的零值是空结构体,这实际上并没有提供任何额外的信息,只是标记了 head 已经访问过。这是使用map的一个常见技巧,用于跟踪哪些元素已经被处理过。
    head=head.Next
  }
  return false
  }
快慢指针 (Floyd 判圈算法) (又称龟兔赛跑算法 )

假想「乌龟」和「兔子」在链表上移动,「兔子」跑得快,「乌龟」跑得慢。当「乌龟」和「兔子」从链表上的同一个节点开始移动时,如果该链表中没有环,那么「兔子」将一直处于「乌龟」的前方;如果该链表中有环,那么「兔子」会先于「乌龟」进入环,并且一直在环内移动。等到「乌龟」进入环时,由于「兔子」的速度快,它一定会在某个时刻与乌龟相遇,即套了「乌龟」若干圈。

我们可以根据上述思路来解决本题。定义两个指针,慢指针每次只移动一步,而快指针每次移动两步。//8ms 4.27MB

func hasCycle(head *ListNode) bool{
    if head==nil||head.Next==nil{
        return false
    }
    slow,fast:=head,head.Next
    for fast!=nil&&fast.Next!=nil{
        
            slow=slow.Next
            fast=fast.Next.Next
            if slow==fast{
                  return true
            }
     
    }
    return false
}
func hasCycle(head *ListNode) bool {
    if head == nil || head.Next == nil {
        return false
    }
    slow, fast := head, head.Next
    for fast != slow {
        if fast == nil || fast.Next == nil {
            return false
        }
        slow = slow.Next
        fast = fast.Next.Next
    }
    return true
}
 二、回文链表

234.回文链表

给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。

将值复制到数组中后用双指针法

想到了用前后两个指针 但是写的时候发现,怎么让right指针指向链表最后一个结点呢 就算指向了,最后一个结点也没有前指针啊 怎么向中间遍历呢

//116ms 10.59MB

//总的时间复杂度:O(2n)=O(n)。空间复杂度:O(n),其中 n 指的是链表的元素个数,我们使用了一个数组列表存放链表的元素值

func isPalindrome(head *ListNode) bool {
   arr:=[]int{}
   for ;head!=nil;head=head.Next{
       arr=append(arr,head.Val)
   }
   n:=len(arr)
   for i,v:=range arr[:n/2]{
       if v!=arr[n-i-1]{
           return false
       }
   }
   return true
}
快慢指针 

避免使用 O(n)额外空间的方法就是改变输入。

将后半部分反转,与前半部分比较,最后将后半部分链表还原。

该方法虽然可以将空间复杂度降到 O(1),但是在并发环境下,该方法也有缺点。在并发环境下,函数运行时需要锁定其他线程或进程对链表的访问,因为在函数执行过程中链表会被修改。(还不懂)

//116ms 9.04MB

时间复杂度:O(n),其中 n 指的是链表的大小。

空间复杂度:O(1)。我们只会修改原本链表中节点的指向,而在堆栈上的堆栈帧不超过 O(1)

 func reverseList(head *ListNode) *ListNode{
       var pre,cur *ListNode=nil,head
       for cur!=nil{
           temp:=cur.Next
           cur.Next=pre
           pre=cur
           cur=temp
       }
       return pre
   }

func endOfFirstHalf(head *ListNode) *ListNode{
    fast:=head
    slow:=head
    for fast.Next!=nil&&fast.Next.Next!=nil{
        fast=fast.Next.Next
        slow=slow.Next
    }
    return slow
}

func isPalindrome(head *ListNode)bool{
    if head==nil{
        return false
    }
    firstHalfEnd:=endOfFirstHalf(head)
    secondHalfStart:=reverseList(firstHalfEnd.Next)

    p1:=head
    p2:=secondHalfStart
    for p2!=nil{
        if p1.Val!=p2.Val{
            return false
        }
       p1=p1.Next
       p2=p2.Next
    }
    firstHalfEnd.Next=reverseList(secondHalfStart)
    return true
}

反转链表那块得随手就能写出来

 

在Go语言中,当你使用for fast.Next != nil && fast != nil作为循环条件时,可能会出现"invalid memory address or nil pointer dereference"(无效的内存地址或空指针解引用)的错误。这是因为在这个条件判断中,你尝试在fast可能为nil的情况下访问它的Next字段。

当一个指针为nil时,尝试解引用它是未定义的行为,并且可能会导致运行时错误。所以,在尝试访问指针的字段之前,你应该首先确保指针不为nil

相反,如果你使用for fast != nil && fast.Next != nil作为循环条件,这是正确的做法。在这个条件下,首先检查fast是否为nil,然后再检查fast.Next是否为nil。这样可以确保在尝试访问fast.Next之前,fast已经被正确地初始化并且不为nil

最后

学到了挺多。学习的成就感莫过于巩固基础+学到新知。

  • 10
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值