python算法刷题(leetcode)——链表

菜鸡的刷题记录,基础知识不会写太多,有时间会写专题复习基础知识。第一轮刷题,所以解法代码可能都比较冗余/难看,主要是追求先有思路和会写。
更多优雅代码请参考解题区或评论区的大佬~

一、 链表(Linked List)

链表,是线性表的链式存储结构。一个链表中有若干个结点,每个结点都包含数据域和地址域两部分。数据域用于存储元素,地址域用于存储前驱或后继的地址。

  • 单链表: 每个结点只有一个地址域的线性链表;
  • 双链表: 每个结点都有两个地址域,分别指向前驱结点和后继结点。

二、实战

做链表题有一个很重要的点,就是在一开始不熟悉的时候要勤动手!多画!把过程画出来!

1. leetcode206 反转链表

在这里插入图片描述
思路: 这道题目的思路比较简单,就是修改指针的指向。画图就能比较直观的看出来。
在这里插入图片描述
具体实现如下:

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

class Solution:
    def reverseList(self, head: ListNode) -> ListNode:
        """迭代解法"""
        newhead = None
        while head:
            next_node = head.next
            head.next = newhead
            newhead = head
            head = next_node
        return newhead
2. leetcode92 反转链表2

在这里插入图片描述
思路: 这一题与上一题不同点在于它是只要求反转链表中间的一段。在这里我们有四个关键的结点的处理需要注意,分别是反转段头结点的前驱、反转段头结点、反转段尾节点以及反转段尾节点的后继。
我们来画图看一下:(鼠标画图好累,自带的画图工具好难用,俺要去瞧瞧有啥好用的画图软件了…先用纸上画的凑合一下…)

在这里插入图片描述
通过画图我们可以看到,除正常反转操作外有两个需要特别处理的结点:

  • 反转链表头结点的前驱a在反转完成之后指向反转后的链表头结点c;
  • 反转链表的头节点b在完成反转后其后继应指向反转前链表尾节点的后继d;
  • 还有一个需要注意的是,当m等于1时需要做一些特殊处理
    具体来看看实现:
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def reverseBetween(self, head: ListNode, m: int, n: int) -> ListNode:
        """用于标记四个特殊节点"""
        modify_prev_node = None
        modify_head_node = head
        modify_tail_node = None
        modeify_next_node = None
        """先定位到要反转的位置"""
        for _ in range(m-1):
            modify_prev_node = modify_head_node
            modify_head_node = modify_head_node.next
        """进行反转链表操作,这里设置许多临时变量是为了防止将前面四个结点的定位改变"""
        change_len = n-m+1
        new_head = modify_head_node
        prev_head = None
        next_head = None
        for _ in range(change_len):
            next_head = new_head.next
            new_head.next = prev_head
            prev_head = new_head
            new_head = next_head
        """反转完成后对四个特殊节点做处理"""
        modify_tail_node = prev_head
        modeify_next_node = new_head
        modify_head_node.next = modeify_next_node
        """这里是为了处理m=1时的特例"""
        if modify_prev_node:
            modify_prev_node.next = modify_tail_node
        else: head = prev_head
        return head
3. leetcode160 链表相交

在这里插入图片描述
这一题会用到一个很常见也很巧妙的方法——双指针。双指针在很多需要在常数/O(n)时间内求解都有很好的应用。
我们用图来看看双指针的解法:
在这里插入图片描述
根据这个思路写一份非常直接粗暴的代码如下:

class Solution:
    def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
        l1, l2 =0, 0
        a, b = headA, headB
        p1, p2 = headA, headB
        # 获取链表长度——遍历链表,时间复杂度O(n)
        while a:
            a = a.next
            l1  += 1
        while b:
            b = b.next
            l2 += 1
        # 较长链表指针先向后移动
        if l1 > l2:
            for i in range(l1-l2):
                p1 = p1.next
        else:
            for i in range(l2-l1):
                p2 = p2.next
        while p1:
            if p1 == p2: return p1
            else:
                p1 = p1.next
                p2 = p2.next
        return None

4. leetcode142 环形链表Ⅱ

在这里插入图片描述
思路:

  1. 首先我们要先判断这个链表有无环(这里与leetcode141一样)。在上一个例题中我们说了双指针的用法是非常常见的,在这里我们同样用双指针来解题。比如我们在环形跑道跑步过程中,有些人跑得快有些人跑得慢,在跑的圈数足够多的情况下,跑得快的一定会追上跑得慢的。我们用双指针来判断有无环也是一样。假设有一个跑得快的指针和一个跑得慢的指针,如果链表有环,这两个指针一定会相遇。
    在这里插入图片描述

  2. 但是快慢指针相遇的点不一定是环的起点, 因此我们判断环的起点还需要一些其他的方法。在这里我们需要用到一些数学计算,具体我们来看看图解:
    在这里插入图片描述
    通过计算我们可以看到,当一个指针从头结点出发,另一个指针从相遇结点相同速度出发,相遇的结点就是环的起点。

根据以上分析代码如下:

class Solution:
    def detectCycle(self, head: ListNode) -> ListNode:
        fast, slow  = head, head
        while True:
            if not (fast and fast.next): return None
            fast, slow = fast.next.next, slow.next
            if fast == slow: break
        fast = head
        while fast != slow:
            slow, fast = slow.next, fast.next
        return fast
5. leetcode86 分隔链表

在这里插入图片描述
思路: 与前两题不同,这个不需要用到双指针来进行解题。但是这里会用到另一种在解决链表问题中常用到的解法——引入头结点/哨兵节点。比如说在链表中插入元素的时候,引入一个哨兵节点就能巧妙解决头结点插入需要差异处理的问题。关于这一题头节点的应用参考下图:
在这里插入图片描述

  1. 分别引入一个less_head和more_head来分别存储分隔链表之后的前半部分和后半部分。
  2. 这样做的好处在于不会破坏结点原有的顺序

具体实现如下:

class Solution:
    def partition(self, head: ListNode, x: int) -> ListNode:
    # 初始化两个头节点
        less_head = less_ptr = ListNode(0)
        more_head = more_ptr = ListNode(0)
        while head:
            if head.val < x:
                less_ptr.next = head
                less_ptr = less_ptr.next
            else:
                more_ptr.next = head
                more_ptr = more_ptr.next
            head = head.next

        more_ptr.next = None
        # 将两个链表连接起来
        less_ptr.next = more_head.next

        return less_head.next
6. leetcode138 复制带随机指针的链表

在这里插入图片描述
先来补充说明一下深拷贝和浅拷贝
深拷贝 (deepCopy) : 在计算机中开辟一个新的内存,存放复制对象。在修改原对象时复制对象不会被改变。
浅拷贝 (shallowCopy) : 在计算机中开辟一个新的内存,存放引用。在修改原对象时复制对象会被改变。

思路:
这一题的难点在于:

  1. 如何复制随机指针的关系
  2. 如何找到随机指针结点对应的位置

拿到这个题目一开始并没有思路,在解题区看到一个大佬解题,详见这里

以下是参考解题中的第一种思路自己动手画的图和写的代码:
在这里插入图片描述

class Solution:
    def copyRandomList(self, head: 'Node') -> 'Node':
        # 1.复制原链表的每一个结点
        new_head = head
        while new_head:
            tmp = Node(new_head.val, new_head.next, None)
            new_head.next = tmp
            new_head = tmp.next
            
        new_head = head
        # 2.复制随机指针(指向原本随机指针指向结点的next)
        while new_head:
            if new_head.random:
                new_head.next.random = new_head.random.next
            new_head = new_head.next.next
        
        # 3.将原链表结点和复制的链表结点分开
        copy_head = Node(-1, None, None)
        new_head = head
        curr = copy_head
        while new_head:
            curr.next = new_head.next
            curr = curr.next
            new_head.next = curr.next
            new_head = new_head.next
        
        return copy_head.next

关于这题的更多解法参见解题区。

7. leetcode21 合并两个排序链表

在这里插入图片描述
思路: 这一题是一个简单题,思路也很简单,就是不断比较两个链表结点的大小,将小的依次连接到新链表上即可。

一种最直接的解法就是利用双指针,很熟悉吧!
用一个指向l1的指针p1和一个指向l2的指针p2,遍历链表,不断比较两个指针指向结点的大小,将节点值较小的连接到新链表上。
下面是python实现:

class Solution:
    def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
        new_head = ListNode(0)
        # tmp是指向合并后结点的指针
        tmp = new_head
        while l1 and l2:
            if l1.val < l2.val:
                tmp.next = l1
                l1 = l1.next
                tmp = tmp.next
            else:
                tmp.next = l2
                l2 = l2.next
                tmp = tmp.next
        if l1:
            tmp.next = l1
        if l2:
            tmp.next = l2
        return new_head.next

当然这题还有递归解法,目前还不是很理解,所以先不放上来,可以参考解题区

链表就先到这里啦!后面再去刷更多的链表题去巩固~
本专题(算法刷题)都是看b站这个视频刷的~一起加油呀!

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值