代码随想录刷题记录(二)——链表

链表

链表理论基础

链表是一种非连续的内存空间,由一系列节点组成,每个节点包含数据部分和指向下一个节点的指针。

数组与链表的区别

  • 数组的优点是可以通过索引快速访问任意位置的元素(时间复杂度为O(1)),但插入和删除元素可能需要移动其他元素(时间复杂度为O(n))。
  • 链表的优点是可以在O(1)时间复杂度内插入和删除节点(如果已经有了节点的引用),但访问特定位置的元素需要从头开始遍历(时间复杂度为O(n))。

python中链表的实现

python原生支持列表list是一个动态数组,可以根据需要自动调整大小,python没有内置的链表数据结构,可以通过类和对象来手动实现链表。

class ListNode: # 这行定义了一个名为 ListNode 的新类。
	# 这是 ListNode 类的构造函数(也称为初始化方法),接受是三个输入
	def __init__(self, val, next = None):
	# self:类实例的引用,用于访问类的属性和方法。
	# val:节点存储的数据值。
	# next:指向链表中下一个节点的引用。参数默认值为 None,表示这个节点的初始状态没有指向任何节点。
		self.val = val # 将构造函数的 val 参数赋值给实例变量 self.val,这样每个 ListNode 对象都会有一个存储数据的属性。
		self.next = next # 将构造函数的 next 参数赋值给实例变量 self.next,这样每个 ListNode 对象都会有一个指向链表中下一个节点的属性。如果未提供 next 参数或显式传入 None,则该属性初始值为 None,表示这个节点是链表的末尾

创建一个简单的列表

# 创建一个类的实例head,它代表链表的头节点,值为10
head = ListNode(10)

# 创建链表的第二个节点,值为20,并将其链接到头节点后面
node2 = ListNode(20)
head.next = node2 # 将node2的引用付给head实例的next属性。

# 创建链表的第三个节点,值为30,并将其链接到第二个节点后面
node3 = ListNode(30)
node2.next = node3

203.移除链表元素

题目描述:删除链表中等于给定值 val 的所有节点。
示例 1: 输入:head = [1,2,6,3,4,5,6], val = 6 输出:[1,2,3,4,5]
示例 2: 输入:head = [], val = 1 输出:[]
示例 3: 输入:head = [7,7,7,7], val = 7 输出:[]
思路
(1)删除操作核心思想:要删除元素的上一个节点的next指针,指向要删除元素的指针指向的下一个节点。
(2) 特殊情况处理:如果head为None或者链表为空,直接返回None,因为没有节点需要删除。
(3)设置虚拟头节点(哑节点)dummy node,为了应对要删除头节点的情况。
(4)采用while循环遍历,判断条件为next指针不为空(链表不是数组或列表,它不提供随机访问的能力。for 循环通常用于可以随机访问元素的数据结构,如数组或列表)

# 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) # 创建虚拟头节点,指针指向head

        # 遍历列表,删除值为val的节点
        cur = dummy_head
        while cur.next:
            if cur.next.val == val:
                # cur.next.val = cur.next.next.val # 无需这行,因为cur.next.next可能是None
                cur.next = cur.next.next
            else:
                cur = cur.next
        return dummy_head.next

707.设计链表

题目描述:在链表类中实现这些功能:

  • get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
  • addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
  • addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
  • addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
  • deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。
    思路
    (1)初始化链表时,记得赋值self.size = 0,往后的每次操作都要注意self.size是否需要同步更改。
    (2)在中间插入一个节点的核心操作:self.cur.next = ListNode(val,self.cur.next),表示cur的指针指向一个值为val,指针指向原本的cur.next的节点。
    (3)处理特殊情况时,要注意index=self.size是否被包含在内
# 类ListNode用来模拟链表
class ListNode:
    def __init__(self,val=0,next=None):
        self.val = val
        self.next = next

# 创建类包含所需的操作
class MyLinkedList:
    def __init__(self):
        # 创建虚拟头节点
        self.dummy_head = ListNode()
        self.size = 0
    # 获取链表中下标为 index 的节点的值。如果下标无效,则返回 -1 。
    def get(self, index: int) -> int:
        # 处理无效值
        if index <0 or index >= self.size:
            return -1
        
        cur = self.dummy_head.next
        for i in range(index):
            cur = cur.next
        
        return cur.val

    # 将一个值为 val 的节点插入到链表中第一个元素之前。在插入完成后,新节点会成为链表的第一个节点。
    def addAtHead(self, val: int) -> None:
        # 这里创建一个节点,要求该节点指向head
        self.dummy_head.next = ListNode(val,self.dummy_head.next)
        self.size += 1


    # 将一个值为 val 的节点追加到链表中作为链表的最后一个元素。
    def addAtTail(self, val: int) -> None:
        cur = self.dummy_head
        while cur.next: #只要指针指向的节点不为空
            cur = cur.next
        cur.next = ListNode(val) # 创建一个信道节点放在末尾
        self.size += 1

    # 将一个值为 val 的节点插入到链表中下标为 index 的节点之前。如果 index 等于链表的长度,那么该节点会被追加到链表的末尾。如果 index 比长度更大,该节点将 不会插入 到链表中。
    def addAtIndex(self, index: int, val: int) -> None:
        # 处理特殊情况
        if index < 0 or index > self.size:
            return
        
        cur = self.dummy_head
        for i in range(index):
            cur = cur.next
        cur.next = ListNode(val, cur.next)
        self.size += 1
    # 如果下标有效,则删除链表中下标为 index 的节点。
    def deleteAtIndex(self, index: int) -> None:
        # 处理特殊情况
        if index<0 or index >= self.size: # 注意,python中index从0开始,这里index=size,则说明指向的位置已经超过了数组本身。
            return
        
        cur = self.dummy_head
        for i in range(index):
            cur = cur.next
        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)

反转链表

题意描述:反转一个单链表。示例: 输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL
思路:对一个中间节点,反转链表意味着,将其原本的指针cur.next指向上一个节点(通常命名为prev)。在遍历链表的过程中,我们需要采用temp记录原本的cur.next,以便cur的指针反转后,能顺利遍历到下一个节点。
注意:这里采用虚拟节点会超出内存,故直接指定prev=None

# 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]:
        prev = None
        cur = head
        
        while cur:
            temp = cur.next
            cur.next = prev # 反转指针

            prev = cur 
            cur = temp
        return prev

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

题目描述:给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
错误思路:开始想着可以基于上一题的代码,创建一个count用来计数,每次循环加1,每当节点数是偶数则交换。但这样会出现下图所示紫色箭头的混乱情况。因为让2节点指向1节点后,1节点的指针仍然指着2节点。
在这里插入图片描述

正确思路:步骤如上述红色箭头。要一次性记录三个节点,pre,cur,以及next。以便在pre和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]:
        if head is None or head.next is None:
            return head

        pre = head
        cur = head.next
        next = head.next.next

        cur.next = pre # 交换
        pre.next = self.swapPairs(next) # 以next为head的后续链表

        return cur #注意这里head不再是头节点

方法二:迭代法

# 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: ListNode) -> ListNode:
        dummy_head = ListNode(0)
        dummy_head.next = head
        cur = dummy_head

        while cur.next and cur.next.next:
            first = cur.next
            second = cur.next.next

            # 交换节点
            first.next = second.next
            second.next = first
            cur.next = second

            # 移动到下一对节点
            cur = first.next

        return dummy_head.next

# 测试代码
def printList(node):
    while node:
        print(node.val, end=" -> ")
        node = node.next
    print("None")

# 创建链表 1 -> 2 -> 3 -> 4
head = ListNode(1, ListNode(2, ListNode(3, ListNode(4))))

# 初始化解决方案对象
solution = Solution()

# 交换链表节点对
swapped_head = solution.swapPairs(head)

# 打印交换后的链表
print("交换后的链表节点值:")
printList(swapped_head)

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

题目描述:给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
进阶:你能尝试使用一趟扫描实现吗?
思路:链表中元素不是连续存储的,所以不能通过索引直接访问任意位置的元素。所以可以采用双指针法,让快指针先行n步,然后快慢指针同步移动,直至快指针移动到链表末尾,此时慢指针指向倒数第n个节点。为了方便起见,我们让慢指针移动到倒数第(n+1)个节点(意味着开始块节点要先走(n+1)步)。由于可能删除第一个节点,我们创建一个虚拟节点方便操作。

# 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

        for i in range(n+1):
            fast = fast.next
        
        while fast:
            fast = fast.next
            slow = slow.next

        slow.next = slow.next.next

        return dummy_head.next

链表相交

题目描述:给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null .
思路:这里的交点不是指数值相等,而是指针相等。两个链表很可能不一样长,所以可以让长的链表的指针先移动几步,使其剩下的长度与短链表相等。然后再遍历,直到A.next == B.next。这意味着先要直到两者的长度,采用

# 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) -> ListNode:
        lenA, lenB = 0, 0
        cur = headA
        # 统计两个链表的长度
        while cur:
            cur = cur.next
            lenA += 1
        cur = headB
        while cur:
            cur = cur.next
            lenB += 1
        curA,curB = headA,headB
        # 使长链表的指针移动
        if lenA >lenB: # 让curB为较长的列表
            curA, curB = curB, curA
            lenA, lenB = lenB, lenA
        
        for _ in range (lenB - lenA): # 让两个链表位于同一起点
            curB = curB.next
        while curA:
            if curA == curB:
                return curA
            else:
                curA = curA.next
                curB = curB.next  

142.环形链表II

题目描述:给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
思路:双指针法。快指针每次走两个节点,慢指针每次走一个节点。如果有环,快慢指针肯定会在环中相遇。这有点类似于追及问题,可以想象两个人从同一起点跑圈,快的那个肯定会套圈慢的那个。

那什么时候相遇呢?相遇时慢指针走过的距离肯定小于一圈。最极端的情况下,当慢指针刚进入环,与快指针几乎处于同一起点出发,那么他们会在慢指针走了一圈时相遇。可以假设相遇时,慢指针走了x+y,快指针走了x+y+n(y+z),结合两者的速度得到下列等式:(x+y)*2 = x+y+n(y+z),得到 x = (n-1)*(y+z)+z。

我们要求的是入口x,可以发现,在快慢指针相遇后。我们可以让两个慢指针分别从链表表头、相遇点同时出发,它们会在入口处相遇。
在这里插入图片描述

# 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]:
        slow,fast = head,head

        while fast and fast.next: # 如果有尽头,肯定是fast先到
            fast = fast.next.next
            slow = slow.next
            
            # 如果快慢指针相遇了
            if fast == slow:
                slow = head

                while slow != fast:
                    slow = slow.next
                    fast = fast.next
                return slow
        
        # 如果循环完了还没有相遇,说明没有环,返回null
        return None

          
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值