【chap4-链表】用Python3刷《代码随想录》

news/2024/7/5 18:55:46

通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域data,另一个是指针域next(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针)

链接的入口点称为链表的头节点head

单链表

1、链表的类型

三种类型:单链表、双链表、循环链表 

双链表

循环链表

2、链表的存储方式

数组在内存中是连续分布的,但链表在内存中不是连续分布的,链表是通过指针域的指针来链接内存中各个节点的。即:链表的节点在内存中是分散存储的,通过指针连在一起

3、链表的定义

# Python定义链表的节点
class ListNode:
    def __init__(self, val, next=None):
        self.val = val
        self.next = next

4、链表的操作

(1)删除节点:

此时节点D依然存留在内存中,只不过从链表中被移除了而已。Python有自己的内存回收机制,不用手动释放

(2 )添加节点:

链表的添加和删除都是时间复杂度为 O(1) 的操作,不会影响其他节点。但是,删除或者添加某个节点的前提是找到操作节点的前一个节点,而查找前一个节点的时间复杂度是 O(n)

5、性能分析

在定义数组的时候,长度是固定的,如果想改动数组的长度,则需要重新定义一个新的数组; 

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


203. 移除链表元素 

203. 移除链表元素

删除元素就是让节点的 next 指针直接指向下一个节点的下一个节点,因为单链表的特殊性,所以需要找到操作节点的前一个节点

两种链表操作方式:

  • 直接使用原来的链表执行删除操作:删除头节点和删除其他节点的操作是不一样的,因为链表的其他节点都是通过前一个节点来删除当前节点的,而头节点没有前一个节点,只要将头节点向后移动一位即可
  • 设置一个虚拟头节点再执行删除操作:设置一个虚拟头节点dummy_head,这样原链表的所有节点都可以按照统一的方式删除了。新的头节点是dummy_head.next
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:
        # 设置一个虚拟头节点
        dummy_head = ListNode(next=head)
        # 设置指针位置
        cur = dummy_head
        while cur.next != None:
            if cur.next.val == val:
                cur.next = cur.next.next
            else:
                cur = cur.next
        return dummy_head.next

707. 设计链表

707. 设计链表

这里以单链表为例。注意,在定义链表之前需要先定义链表节点的结构体

(1)获取下标为 index 的节点的值:注意 cur 初始化为 dummy_head.next,也就是真正的head

(2)头部插入节点:注意操作顺序,十分重要!!!还要记得 size 要 +1

(3)尾部插入节点:记得 size 要 +1

(4)第n个节点前插入节点:操作第n个点,第n个点一定是cur_next,这样才能用 cur 去控制第n个点是增加还是删除。注意操作顺序,十分重要!!!以及记得 size 要 +1

(5)删除下标为 index 的节点:删除下标为 index 的节点(cur.next),必须要知道它的前一个节点(cur),让它的前一个节点指向该节点的后一个节点,才能删除该节点。以及记得 size 要 -1

class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next


class MyLinkedList:

    def __init__(self):
        self.dummy_head = ListNode(0)
        self.size = 0


    def get(self, index: int) -> int:
        if index<0 or index>self.size-1:
            return -1
        cur = self.dummy_head.next
        while index:
            cur = cur.next
            index-=1
        return cur.val


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


    def addAtTail(self, val: int) -> None:
        new_node = ListNode(val)
        cur = self.dummy_head
        while cur.next != None:
            cur = cur.next
        cur.next = new_node
        self.size+=1


    def addAtIndex(self, index: int, val: int) -> None:
        if index>self.size:
            return
        else:
            new_node = ListNode(val)
            cur = self.dummy_head
            while index:
                cur = cur.next
                index-=1
            new_node.next = cur.next
            cur.next = new_node
            self.size+=1


    def deleteAtIndex(self, index: int) -> None:
        if index<0 or index>self.size-1:
            return
        cur = self.dummy_head
        while index:
            cur = cur.next
            index-=1
        cur.next = cur.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. 反转链表

206. 反转链表

法1:双指针法

  • cur 先指向 head,这是第一个指针
  • 第二个指针,就是反转之后,cur指向它的后面了,定义为 pre(其实就是 cur 的前一位),初始化为空指针
  • 遍历链表,直到 cur 指向空指针结束
  • 遍历时,需要第三个指针 tmp,在没反转之前,保存 cur 的下一个指针,再反转
  • 最后新链表的头节点就是 pre
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
        cur = head
        pre = None 
        while cur != None:
            tmp = cur.next
            cur.next = pre  # 改变方向
            # 指针往后遍历
            pre = cur
            cur = tmp
        return pre

法2:递归法

同样是当 cur 为空的时候循环结束,不断将 cur 指向 pre

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:

        def revserse(cur, pre):
            if cur == None:
                return pre
            tmp = cur.next
            cur.next = pre
            return revserse(tmp,cur)
        
        return revserse(head, None)

24. 两两交换链表中的节点

24. 两两交换链表中的节点

几个重点: 

(1)操作指针一定要指向要反转的两个节点的前一个结点,如要交换节点1和节点2,此时操作指针应指向dummy_head(节点1的前一个节点)

(2)遍历什么时候结束?    while cur.next != None and cur.next.next != None:

  • 链表节点个数为偶数时,cur.next = null
  • 链表节点个数为奇数时,cur.next.next = null
  • 链表节点个数为0,即空链表时,cur.next = null(此时cur指向虚拟头节点)

(3)交换相邻两个元素时,要注意操作的先后顺序

  • 初始时,cur指向虚拟头节点

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
        dummy_head = ListNode(next=head)   # 创建虚拟头结点
        cur = dummy_head   # 当前操作指针指向虚拟头结点

        while cur.next != None and cur.next.next != None:
            tmp = cur.next   # 保存节点1
            tmp1 = cur.next.next.next   # 保存节点3
            cur.next = cur.next.next   # 虚拟头结点指向节点2
            cur.next.next = tmp   # 节点2指向节点1
            tmp.next = tmp1   # 节点1指向节点3
            cur = cur.next.next   # 将当前操作指针往后移动2位

        return dummy_head.next   # 返回交换后链表的头节点

19. 删除链表的倒数第N个结点 

19. 删除链表的倒数第 N 个结点

要删某个节点,操作指针要指向该节点的前一个节点

如何找到倒数第N个节点?

  • 虚拟头节点:好处是不需要对操作节点是不是头节点进行特殊的判断,而可以采用统一的方式进行删除   dummy_head = ListNode(next=head)
  • 定义快慢指针,让快指针先移动N步,然后快慢指针再同时移动,直到快指针指向了空节点,此时慢指针就指向了要删的倒数第N个节点
  • 但问题是,要删倒数第N个节点,操作指针要指向该节点的前一个节点,即慢指针应指向倒数第N个节点的前一个节点
  • 故最终:定义快慢指针,让快指针先移动N+1步,然后快慢指针再同时移动,直到快指针指向了空节点,此时慢指针就指向了要删的倒数第N个节点的前一个

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
        # 定义虚拟头节点
        dummy_head = ListNode(next=head)
        # 定义快慢指针,初始值都为虚拟头节点
        fast = slow = dummy_head

        # 让快指针先走n+1步
        for i in range(n+1):
            fast = fast.next
        # 然后快慢指针再同时走,直到快指针为空
        while fast != None:
            fast = fast.next
            slow = slow.next
        # 此时慢指针指向倒数第n个节点的前一个
        slow.next = slow.next.next
        return dummy_head.next

160. 相交链表

160. 相交链表

注:交点指引用完全相同,即内存地址完全相同的交点 

若一个指针到达链表的结尾,就切换到另一个链表的头节点继续移动 

LeetCode刷题|python版本|160题|相交链表_哔哩哔哩_bilibili

分析:设第一个公共节点为node,链表A的节点数量为a,链表B的节点数量为b,两链表公共尾部的节点数量为c,则有 

  • 头节点 headA 到 node 前,共有 a - c 个节点
  • 头节点 headB 到 node 前,共有 b - c 个节点

考虑构建两个节点指针 curA、curB,分别指向两链表头节点 headA、headB,做如下操作:

  • 指针 curA 先遍历完链表A,再开始遍历链表B,当走到 node 时,共走步数为:a+(b-c)
  • 指针 curB 先遍历完链表B,再开始遍历链表A,当走到 node 时,共走步数为:b+(a-c)

可以发现它们走的步数相同,所以此时指针 curA 和 curB 重合,且有两种情况:

  • 若两链表有公共尾部(即c>0):指针 curA 和 curB 同时指向第一个公共节点node
  • 若两链表无公共尾部(即c=0):指针 curA 和 curB 同时指向null

故返回 curA 和 curB 任一指针即可

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> Optional[ListNode]:
        # 定义两指针,分别指向两链表的头节点
        curA = headA
        curB = headB

        # 两指针不相等时就同时向后移动
        while curA != curB:
            curA = curA.next if curA else headB   # 若curA走到空,跳到B
            curB = curB.next if curB else headA
        # 当两指针相等时返回任意一个指针即可
        return curA

算法复杂度:

  • 由于两指针最多遍历两个链表,故时间复杂度为O(m+n),其中m和n为两链表的长度
  • 由于不需要开辟额外空间,故空间复杂度为O(1)

142. 环形链表II 

142. 环形链表 II

这题有两小问:

  • 判断链表是否有环:快慢指针都从头节点出发,快指针每次走2步,慢指针每次走1步,若能相遇则有环
  • 寻找环的入口:在相遇节点处定义一个指针index1,在头节点处定一个指针index2,让index1和index2同时移动,每次移动一个节点,那么它们相遇的地方就是环的入口节点
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
        # 定义快慢指针
        fast, slow = head, head
        while fast != None and fast.next != None:    # 因为fast每次走两步
            fast = fast.next.next
            slow = slow.next
            if fast == slow:   # 若快慢指针相遇
                index1 = fast    # 相遇点
                index2 = head    # 头节点
                while index1 != index2:    # 两者相遇时终止
                    index1 = index1.next
                    index2 = index2.next
                return index1
        return None

http://www.niftyadmin.cn/n/4611454.html

相关文章

Linux 文件搜索

为什么80%的码农都做不了架构师&#xff1f;>>> 文本搜索&#xff1a;grep &#xff08;文本中找内容&#xff09; Linux系统中grep命令是一种强大的文本搜索工具&#xff0c;grep允许对文本文件进行模式查找。如果找到匹配模式&#xff0c; grep打印包含模式的所有…

JavaScript如何减少if(装x牛逼)

正常的写法是这样的 if (newValue 2) {this.is_abnormal true;} else {if (this.form.motor ! 2 && this.form.die ! 2 && this.form.position ! 2) {this.is_abnormal false;}}使用return和&&符号之后 if (newValue 2) {this.is_abnormal true;r…

[转]java提高篇(十一)-----强制类型转换

在java中强制类型转换分为基本数据类型和引用数据类型两种&#xff0c;这里我们讨论的后者&#xff0c;也就是引用数据类型的强制类型转换。 在Java中由于继承和向上转型&#xff0c;子类可以非常自然地转换成父类&#xff0c;但是父类转换成子类则需要强制转换。因为子类拥有比…

NodeJS-express框架搭建

首先新建一个文件夹C:\test在该文件夹打开cmd安装express模块&#xff0c;npm install express 再运行npm init初始化项目这时候的目录是test{node_modules,package-lock.json,package.json} 在test目录下创建文件app.js 在test目录下创建文件夹route 在route文件夹下创建文件u…

express静态资源配置

目录如下 创建views.js const router require(express).Router(); const fs require(fs); const { resolve } require(path); router.get(/index.html, (req, res) > {// 这里设置utt-8否则返回的buffer数据格式&#xff0c;会自动下载fs.readFile(resolve(./)/views/in…

说一说我理解的css

什么是CSS 从字面上来理解&#xff0c;css的全称是Cascading Style Sheets&#xff0c;翻译成为中文&#xff0c;就是层叠样式表&#xff0c;为啥子要叫作这个名字&#xff1f; 先来看看这个层字&#xff1a;从字面意义上来理解&#xff0c;就是一层一层的意思 我们一般都是怎么…

vue封装数字键盘

先看效果图 组件XKeyboard.vue <template><div id"XKeyBoard" style"display:none;" :style"{width: widthvw,height: heightvw,left:leftpx,top:toppx}" ref"XKeyBoard"><div class"x-top"><div cli…

spring中使用spring mvc jdbc操作数据库

为什么80%的码农都做不了架构师&#xff1f;>>> 初次接触Java Spring MVC, 正准备选个适合自己的orm用, Hibernate我感觉还是有点复杂, Mybatis一样如此. 这是我最后确定的orm, spring自带的jdbc, 蛮适合我! 先看下我的配置 web.xml <?xml version"1.0&qu…