python: LeetCode之设计链表(小白)

目录

什么是链表?

数组:

链表:

新手注意:

解题思路:

创立链表节点类:

先写加尾函数:

再写加头函数:

其次get函数:

在index的位置添加节点的函数:

删除节点函数:


链表图文学 - LeetBook - 力扣(LeetCode)全球极客挚爱的技术成长平台https://leetcode.cn/leetbook/read/linked-list/jy291/

本文为个人记录学习及思考过程所用,如文中有任何错误,望各位大佬海涵,并指出错误。

注:连接为LeetCode内部《链表图文学》一书中的《设计链表》题目,题库中也包含该题。

笔者的代码属菜鸟级别,提交后运行速度不佳,内存占用较少,说明优化空间较大,但代码简单易懂,适合新手。

什么是链表?

链表的输出和列表(数组)相似,打个比方:

数组:

将内存比作鸡蛋盒,储存内容为鸡蛋,那么数组的储存方式为:顺序摆放,从0开始,依次标号(索引)。这样索引非常容易,我只需要找出对应标号(索引)即可取出“鸡蛋”。但如果我想在已经排好序的“鸡蛋”中删除或添加一个或多个“鸡蛋”,就会非常困难。(最末尾的添加非常简单)因为我需要挪动一部分“鸡蛋”,给新来的腾位置。所以数组添加元素较难,但索引容易。

链表:

将链表(包括内存的值和指针)比作别针,链表则为一个一个单独的别针散乱分布,(拿上个例子来说,就是鸡蛋盒内散乱分布着的鸡蛋)别针可以随意解开、连接、添加,当我将多个别针连在一起的时候,从头拎起,捏住的“别针”,就是链表头,“指针”则指向下一个链表头——可以理解为别针与别针相连处。因此,使用链表添加、删除元素很容易,但是索引、遍历长度较为繁琐。

新手注意:

链表并不是python内部自带的内容,因此我们无法像创建数组一样,随时随地创建一个链表。一般都需要创建一个“链表类”。

接触链表之初,我一直无法理解链表。因为有的教程创建了一个类,即为“链表类”,而有的教程则创建了两个类,“链表类”,“表头类”。一度让我十分混乱——当我将链表一个一个连接在一起的时候,我自然而然不就知道谁是链表头了吗?一度让我怀疑大佬们所言:“链表超级简单的哩。”

其实不然,这时候的我思维很乱。我理解了链表的作用机制以及链表的利弊。但我依然无法理解链表的具体操作是什么,如何遍历链表。

链表类,其实是“链表节点类”:即,我使用一个类,在类中应用一个init函数,函数内部参数有三个,分别是:self, val, next。相当于创建了一个“别针”,而不是连成一串的别针。


解题思路:

题目要求设计一个链表。给出了一个大类,内部封装了五个函数。

在做题目的时候,切勿按照函数顺序来做,应该先读题目对于每个函数的要求,并且清楚题目中所给类的用意——连接链表。

虽然题目没有给出init函数,但是设计“链表节点类”的函数不应该和“连接链表类”放在一起,所以第一步——创立“节点类”。

创立链表节点类:

class ListNode:
    # self.val = 0
    # self.next = listNode()
    def __init__(self, value=0, node=None):
        self.val = value
        self.next = node

这样,我就创建了一个链表节点(别针),其值为我们传入的参数value,指针指向的None。

注意:在调用第二个类中函数的时候,我们每传入一个参数(下文中传入的参数都为int类型,而非链表节点),都要调用节点类,将int转为链表节点。

(插一个题外话,作为纯纯的小白,我在做这个题目的时候,一直都按照顺序来做,所以,在做到get函数的时候,(没错,就是第一个函数),我就做不下去了。因为问题很多:1.我需要从表头遍历,可是表头在那里?2. 我还要知道链表长度,长度怎么算?  所以,可以从“加尾”函数开始)

先写加尾函数:

在加尾的时候需要考虑:

1. 链表是否为空?如果为空,加尾就相当于是链表头。

因此,我在第二个类中写了一个init函数:

class MyLinkedList:

    def __init__(self):
        self.root = None
        self.size = 0

(这里我维护了一个self.size,表示的是整个链表的长度,方便调用get函数时使用,注意,这里初始值为0,在进行:加、添、删等操作的时候需要更新self.size的值)

该类我只调用一次,所以只生成一个self.root,是空的。

加尾的时候,我只需要判断self.root是否为None,如果是,则直接将尾节点作为self.root,如果不为空,则设置一个单指针cur,从self.root开始,依次向后遍历,直到cur.next == None时,将尾节点加上即可。

    def addAtTail(self, val: int) -> None:
        newNode = ListNode(val, None)
        if self.root == None:
            self.root = newNode
        else:
            cur = self.root
            while cur.next != None:
                cur = cur.next
            cur.next = newNode

        self.size += 1

再写加头函数:

加头函数相对容易,我们已知最初类中设置了一个隐藏的链表头:self.root,(注意,不是虚拟链表头)

我只需要先调用第一个类,将val转为链表节点node,再将指针指向self.root,并把node设为self.root。因为无论怎么变,我都需要self.root来标记链表的头结点,方便添加、删除。

依然不要忘记self.size的维护。

    def addAtHead(self, val: int) -> None:
        node = ListNode(val, None)
        node.next = self.root
        self.root = node
        self.size += 1

其次get函数:

在get的时候注意题目的要求:

  • get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。

什么叫索引无效?

index < 0; index >= self.size

注意,这里的index从0开始,和列表的索引值一样。

在判断完index在范围内,利用cur来遍历,输出index == 0时,对应的链表值。

    def get(self, index: int) -> int:
        # cur = self.root
        # if self.root == None:
        #     print("root == None")
        # while cur != None:
        #     print(cur.val, end=' ')
        #     cur = cur.next
        # print("size=", self.size)
        # print(" ")

        if index < 0 or index >= self.size:
            # print("lelelelel")
            return -1
        cur = self.root
        while cur != None:
            if index == 0:
                return cur.val
            cur = cur.next
            index -= 1

注意:if语句一定要在index运算之前进行,避免出错。

另外,我在该函数中设置了检测get函数是否正确的代码段。

在index的位置添加节点的函数:

首先还是读题:

        在链表中的第 index 个节点之前添加值为 val  的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。

头尾只需要调用之前的函数即可(但要注意,self.size已经在之前的函数中加过了,所以不需要再加一次)

如果符合条件,遍历到index == 1时,直接添加节点,但要注意,先将要添加节点的指针指向cur后面的节点,再将遍历的cur的指针指向添加节点,否则后面的指针会丢。


    def addAtIndex(self, index: int, val: int) -> None:


        if index <= 0:
            self.addAtHead(val)
        elif index == self.size:
            self.addAtTail(val)
        elif index > self.size:
            return
        else:
            node = ListNode(val, None)
            cur = self.root
            while(index):
                if index == 1:
                    break
                cur = cur.next
                index -= 1
            
            node.next = cur.next
            cur.next = node
            self.size += 1

删除节点函数:

依然是读题:

  • deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。

步骤为:

1. 判断index是否有效;

2. 遍历,在index == 1时停下,执行删除操作。

    def deleteAtIndex(self, index: int) -> None:

        if index >= self.size or index < 0:
            return
        elif index == 0:
            self.root = self.root.next
            self.size -= 1
            return
        else:
            cur = self.root
            while(index):
                if index == 1:
                    cur.next = cur.next.next
                    self.size -= 1
                    return
                index -= 1
                cur = cur.next

别忘了维护self.size!

最后:给链表debug是真的慢啊……

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值