代码随想录算法训练营第三天 | 203.移除链表元素 707.设计链表 206.反转链表


链表理论基础

理论链接
虽然学习过链表,但是还是要记录一下没有学过的细节

链表的种类

在这里插入图片描述
在这里插入图片描述

链表节点的定义:

接下来说一说链表的定义。
链表节点的定义,很多同学在面试的时候都写不好。
这是因为平时在刷leetcode的时候,链表的节点都默认定义好了,直接用就行了,所以同学们都没有注意到链表的节点是如何定义的。
而在面试的时候,一旦要自己手写链表,就写的错漏百出。

文章里面给了c++的构造函数;python的节点的构造函数应该会简单点;给出我学习的python基础课程里面的链表节点的定义【具体见我的onenote–2202405数据结构笔记本day01线性表】
课程中介绍了链式表的连续和非连续存储

# 创建节点类
class Node:
    """
    思路 : *自定义类视为节点类,类中的属性为数据内容
          *写一个next属性,用来和下一个 节点建立关系
    """
    def __init__(self,val,next = None):
        """
        val: 有用数据
        next: 下一个节点引用
        """
        self.val = val
        self.next = next

节点的操作【复习一下】

在这里插入图片描述
在这里插入图片描述

性能分析

在这里插入图片描述
数组在定义的时候,长度就是固定的,如果想改动数组的长度,就需要重新定义一个新的数组。

链表的长度可以是不固定的,并且可以动态增删, 适合数据量不固定,频繁增删,较少查询的场景。

具体分析比较可见于python课程的pdf

203.移除链表元素

题目链接
讲解链接
在这里插入图片描述
在这里插入图片描述

删除链表元素

如果不使用虚拟头节点,删除列表元素需分情况:

移除头结点和移除其他节点的操作是不一样的,因为链表的其他节点都是通过前一个节点来移除当前节点,而头结点没有前一个节点。
所以头结点如何移除呢,其实只要将头结点向后移动一位就可以,这样就从链表中移除了一个头结点。

  1. 头节点:head指向head.next
  2. 非头节点:cur的next指向cur.next.next

如果使用虚拟头节点,就不需要分类了,更加直接

思路

方法一:不使用虚拟头节点

分类讨论:
🧨注意一开始是while而不是if

🎃自己写遇到的小问题

  1. 注意:==这个题目传入的是链表的head节点。。。。表述感觉不清楚
  2. python里面是none,c++里面是null,,,,
  3. 注意一开始判断头节点的情况下,需要使用while而不是if;因为可能出现从头开始就需要删除好几次的情况,如下在这里插入图片描述
  4. 注意:node不为空的判断
# Definition for singly-linked list.
class ListNode(object):
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next
#传入的是写好的链表的head
class Solution(object):
    def removeElements(self, head, val):
        """
        :type head: ListNode
        :type val: int
        :rtype: ListNode
        """
        #==这个题目传入的是链表的head。。。。表述感觉不清楚
        #首先是头节点
        while head!=None and head.val == val: #这里是while,要注意!!
        #要判断是否为空指针,否则没有val且考虑到最后一个节点的情况
            head = head.next

        curr = head
        if curr != None:
            print(head.val)
        while curr != None and curr.next != None :
            if curr.next.val == val:#记得是比较val。。。。
                curr.next = curr.next.next
            else:
                curr = curr.next

        return head
        

方法二 使用虚拟头节点

思路:设置一个虚拟头节点,原链表的所有节点可以按照统一的方式进行移除了。
在这里插入图片描述
🎀注意:
在这里插入图片描述
返回的是虚拟头节点的下一个而不是head,因为原先的head有可能被删掉了

class Solution(object):
    def removeElements(self, head, val):
        """
        :type head: ListNode
        :type val: int
        :rtype: ListNode
        """
        dammy_head = ListNode(next=head)
        curr = dammy_head #注意current指针指向的是虚拟头节点,而不是它的next
        while curr.next != None:
            if curr.next.val == val:
                curr.next = curr.next.next
            else:
                curr = curr.next
        return dammy_head.next

707.设计链表

题目链接
讲解链接

建议: 这是一道考察 链表综合操作的题目,不算容易,可以练一练 使用虚拟头结点
在这里插入图片描述

思路

注意事项:

  1. 首先要注意题目中说下标从0开始,所以index应该是从0到size-1;
  2. 需要定义一个额外的curr指针来遍历而不是head,因为head不能动,最后要返回的是head

获取第n个节点的值

首先curr指向dammy-node 的下一个节点;
判断语句写while(n),然后使用n-1来迭代;【🎀Tip:记住,用需要查找的n递减来作为判断循环条件】

while里面是写n还是n+1,n-1,考虑极端情况,如果n为0,while里面应该是不执行,直接返回curr指向的head的值,所以while里面是n;

头部插入节点

注意插入节点的顺序:先将新节点指向dammy-node 的下一个,再将dammy-node的下一个指向新的节点;顺序倒了的话就会把dammy-next的节点丢了

尾部插入节点

第n个节点之前插入节点

curr指针一定要指向第n个节点之前的那个节点,才能操作
curr一开始指向dammy-node节点【每个小任务curr的起始位置都不一样】

删除第n个节点

第n个节点应该是curr的next,这样才可以把第n个节点去除掉;【与尾部插入节点同理】
curr一开始应该指向dammy-node

我的总结

关于curr一开始是指向dammy-node还是dammy-node.next,取决于最后在操作是curr是指在待操作节点上,还是待操作节点前一个上;目前只有两种情况

  1. 返回index对应的值的时候:curr最后应该指在index节点上,所以初始位置在dammy-node 的下一个节点
  2. 插入:一定是在要知道待插入位置的前面一个节点,所以curr初始位置在dammy-node上

代码

注意事项

  • 需要自己补充ListNode类,此外,MyLinked类需要自己初始化
  • 需要注意代码的容错性:也就是要包括输错的情况,例如,delete的index超过链表长度了
    • 具体操作为:设置链表长度属性,每次操作之前判断
    • del和add的操作记得size的变化
    • 记住下面的这个例子:

在这里插入图片描述

"""
全部都是我自己写的,==写一天debug好久💀
"""
class Node(object):
    def __init__(self,val=0,next=None):
        self.val = val
        self.next = next

class MyLinkedList(object):

    def __init__(self):
        # self.head =Node()
        self.dammy_head = Node()
        self.size = 0


    def get(self, index):
        """
        :type index: int
        :rtype: int
        """
        if index < 0 or index >= self.size:
            return -1
        curr = self.dammy_head.next
        while index:#index>0
            curr = curr.next
            index -= 1
        return curr.val

    def addAtHead(self, val):
        """
        :type val: int
        :rtype: None
        """
        new_node = Node(val,self.dammy_head.next)
        self.dammy_head.next = new_node
        #记得size+1
        self.size += 1


    def addAtTail(self, val):
        """
        :type val: int
        :rtype: None
        """
        curr = self.dammy_head
        new_node = Node(val,None)
        while curr.next != None:
            curr = curr.next
        curr.next = new_node
        self.size += 1

    def addAtIndex(self, index, val):
        """
        :type index: int
        :type val: int
        :rtype: None
        注意题目里面说的是插入到index这个节点之前
        """
        if index < 0 or index > self.size:
            return
        
        curr = self.dammy_head
        while index:
            curr = curr.next
            index -= 1
        curr.next = Node(val,curr.next)
        self.size += 1

    def deleteAtIndex(self, index):
        """
        :type index: int
        :rtype: None
        """
        if index < 0 or index > self.size-1: #注意这里是self.size-1
            return
        curr = self.dammy_head
        while index:
            curr = curr.next
            index -= 1
        curr.next = curr.next.next
        self.size -= 1


# Your MyLinkedList object will be instantiated and called as such:
# obj = MyLinkedList()
# param_1 = obj.get(index)
# obj.addAtHead(val)
# obj.addAtTail(val)
# obj.addAtIndex(index,val)
# obj.deleteAtIndex(index)

206.反转链表

题目链接
讲解链接
在这里插入图片描述
是面试里面常常考的第一道基础题:考察数据结构

思路

完全可以新建一个链表,但是面试官会问你满足空间复杂度为O(1)要怎么写

方法一 双指针法

时间复杂度: O(n) 空间复杂度: O(1)
定义一个前面的指针pre来方便反转

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

  • 起始位置:首先定义一个cur指针,指向头结点,再定义一个pre指针,初始化为null。
  • 遍历结束是,cur指向了Null了
  • 转换的中间过程:先移动pre,再移动curr顺序不能换
    首先要把 cur->next 节点用tmp指针保存一下,也就是保存一下这个节点。
    为什么要保存一下这个节点呢,因为接下来要改变 cur->next 的指向了,将cur->next 指向pre ,此时已经反转了第一个节点了。
class Solution(object):
    def reverseList(self, head):
        """ 
        :type head: ListNode
        :rtype: ListNode
        """
        pre,curr = None,head
        while curr != None:
            tmp = curr.next
            curr.next = pre
            pre = curr
            curr = tmp
        return pre

方法二 递归写法

时间复杂度: O(n), 要递归处理链表的每个节点
空间复杂度: O(n), 递归调用了 n 层栈空间
递归思路与双指针一样,代码更加简洁,但是比较晦涩难懂

  • 这里额外调用了一个reverse函数来递归

自己写的错误点

  1. 调用函数忘了加self.开头,总是报错找不到reverse函数
  2. 递归忘了写return
# Definition for singly-linked list.
# class ListNode(object):
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution(object):
    def reverse(self,curr,pre):
        if curr == None:#判断已经递归到最后了(结束的判断和双指针法是相同的)
            return pre
        #如果没有到最后的话就进行反转
        tmp = curr.next
        curr.next = pre
        # pre = curr
        # curr = tmp
        return self.reverse(tmp,curr)#少了return
    def reverseList(self, head):
        """ 
        :type head: ListNode
        :rtype: ListNode
        """
        return self.reverse(head,None) #要写self.这都能错

总结

花的时间还是长了,花了四个小时。。。。。希望自己下次记笔记快一点。。。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值