音乐播放器
R1yhtp的blog
 
文章 标签
4

Powered by Gridea | Theme: Fog
载入天数...
载入时分秒...

从零开始的算法之路(链表篇)

颓废了5天,继续更新自己的学习之路qaq,真的好想摆啊,最近感觉活着好累(苦啊R1),扯远了进正题吧
链表问题相对容易掌握。 不要忘记 "双指针解法" ,它不仅适用于数组问题,而且还适用于链表问题。还是直接上题目比较快(学过数据结构会发现链表其实也就那样,让我们把他拿下!)

算法一 删除链表中的节点

题目:
有一个单链表的 head,我们想删除它其中的一个节点 node。
给你一个需要删除的节点 node 。你将 无法访问 第一个节点  head。
链表的所有值都是 唯一的,并且保证给定的节点 node 不是链表中的最后一个节点。
删除给定的节点。注意,删除节点并不是指从内存中删除它。这里的意思是:

给定节点的值不应该存在于链表中。
链表中的节点数应该减少 1。
node 前面的所有值顺序相同。
node 后面的所有值顺序相同。

自定义测试:
对于输入,你应该提供整个链表 head 和要给出的节点 node。node 不应该是链表的最后一个节点,而应该是链表中的一个实际节点。
我们将构建链表,并将节点传递给你的函数。
输出将是调用你函数后的整个链表。

一看就是课本老题目了,甚至期末还考了,下面直接给出完美课本解法()两行直接搞定

解题方法

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

class Solution:
    def deleteNode(self, node):
        """
        :type node: ListNode
        :rtype: void Do not return anything, modify node in-place instead.
        """
        node.val=node.next.val
        node.next=node.next.next

算法题二 删除链表的倒数第N个节点

题目:给你一个链表,删除链表的倒数第 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 = ListNode(0)
        # 创建虚拟头节点
        dummy.next = head
        
        # 初始化快慢指针
        fast = dummy
        slow = dummy
        
        # 快指针先移动 n 步
        for _ in range(n):
            fast = fast.next
        
        # 快慢指针同时移动,直到快指针到达链表末尾
        while fast.next:
            fast = fast.next
            slow = slow.next
        
        # 删除倒数第 n 个节点
        slow.next = slow.next.next
        
        # 返回链表的头节点
        return dummy.next

解题方法二 递归

# 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]:
        # 定义一个递归函数,返回当前节点的倒数位置
        def dfs(node):
            if not node:
                return 0  # 到达链表末尾,返回 0
            count = dfs(node.next)  # 递归调用
            if count == n:  # 如果下一个节点是倒数第 n 个节点
                node.next = node.next.next  # 删除下一个节点
            return count + 1  # 返回当前节点的倒数位置

        # 如果删除的是头节点
        if dfs(head) == n:
            return head.next
        return head

算法三 反转链表

题目:给你单链表的头节点 head ,请你反转链表,并返回反转后的链表

解法1

# 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: ListNode) -> ListNode:
        prev = None
        curr = head
        
        while curr:
            next_node = curr.next  # 保存下一个节点
            curr.next = prev       # 反转当前节点的指针
            prev = curr            # 移动 prev 到当前节点
            curr = next_node       # 移动 curr 到下一个节点

        return prev  # prev 是新的头节点

算法4 合并两个有序链表

题目:
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

我的思路是先相连后提取所有的val,之后重新赋值

解法一

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]:
        if not l1:
            return l2
        if not l2:
            return l1

        # 连接两个链表
        current = l1
        while current.next:
            current = current.next
        current.next = l2

        # 提取所有节点的值
        values = []
        current = l1
        while current:
            values.append(current.val)
            current = current.next

        # 对值进行排序
        values.sort()

        # 重新赋值给新链表
        dummy = ListNode()
        current = dummy
        for val in values:
            current.next = ListNode(val)
            current = current.next

        return dummy.next

不过,这个解法不是很推荐,虽然很容易想到,因为:
缺点:
1.时间复杂度高:
提取所有节点的值需要遍历链表,时间复杂度为 O(n + m)。
对值进行排序的时间复杂度为 O((n + m) * log(n + m))。
重新赋值需要再次遍历链表,时间复杂度为 O(n + m)。
总时间复杂度为 O((n + m) * log(n + m)),比双指针法的 O(n + m) 更高。

2.空间复杂度高:
需要额外的空间存储所有节点的值,空间复杂度为 O(n + m)。
双指针法的空间复杂度仅为 O(1)。

3.破坏原链表结构:
连接链表会破坏原链表的结构,如果后续还需要使用原链表,可能会导致问题。

解法二 一般解法

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

def mergeTwoLists(l1: ListNode, l2: ListNode) -> ListNode:
    # 创建一个虚拟头节点,方便操作
    dummy = ListNode()
    current = dummy
    
    # 遍历两个链表
    while l1 and l2:
        if l1.val < l2.val:
            current.next = l1
            l1 = l1.next
        else:
            current.next = l2
            l2 = l2.next
        current = current.next
    
    # 如果其中一个链表还有剩余节点,直接接上
    if l1:
        current.next = l1
    else:
        current.next = l2
    
    # 返回合并后的链表头节点
    return dummy.next

时间复杂度:O(n + m),其中 n 和 m 分别是两个链表的长度。我们需要遍历两个链表的所有节点。
空间复杂度:O(1),我们只使用了常数级别的额外空间。
这个方法高效且易于理解,适用于大多数合并两个有序链表的场景。

算法五 回文链表

给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?

如果是空间复杂度O(1)就不能取值比较哩,采用双指针秒吧

解题方法 双指针

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    # 使用列表 + 双指针方式求解
    def isPalindrome(self, head: ListNode) -> bool:
        arr = [] # 临时空间存储整个链表

        while head:
            arr.append(head.val)
            head = head.next

        # 使用双指针 
        left, right  = 0, len(arr) -1
        while left < right:
            if arr[left] != arr[right]:
                return False
            else:
                left += 1
                right -= 1
        return True
        # return arr == arr[::-1] # 方法2 使用Python 列表切片反转特性

吐槽一哈(共勉?maybe)

目前是2025年3月11日 星期二的23:17,明天就要提问单词了hhh,还没开始背,不过因为最近密码的太压抑了,想发泄一哈,写到算法笔记里面吧,首先这俩月我简直快疯了(苦啊R1yhtp QAQ)看着身边的朋友成功和幸福说不羡慕是假的,然后我也不知道要怎么办了,目前保研的希望估计是没了,然后想着卷卷技术考个研得了,可是就是提不起来劲,哎又伤心了()
分享一首歌,从我初识这首歌已经两年了,几乎平均一天一遍hhhh(当然是平均,有的时候一天循环几十遍)

https://music.163.com/#/song?id=30431366


希望未来一切安好,愿你我顶峰相见