数据结构(Python实现)------ 链表

数据结构(Python实现)------ 链表

单链表

基本概念

在这里插入图片描述
在这里插入图片描述

添加操作 - 单链表

如果我们想在给定的结点 prev 之后添加新值,我们应该:
在这里插入图片描述
与数组不同,我们不需要将所有元素移动到插入元素之后。因此,您可以在 O(1) 时间复杂度中将新结点插入到链表中,这非常高效。

在开头添加结点
在这里插入图片描述

删除操作 - 单链表

在这里插入图片描述

删除第一个结点
在这里插入图片描述

设计链表

class MyLinkedList(object):
    def __init__(self):
        self.linkedlist = list()

    def get(self,index):
        if index < 0 or index >= len(self.linkedlist):
            return -1
        else:
            return self.linkedlist[index]

    def addAtHead(self,val):
        self.linkedlist.insert(0,val)

    def addAtTail(self,val):
        self.linkedlist.append(val)

    def addAtIndex(self,index,val):
        if index <= len(self.linkedlist):
            self.linkedlist.insert(index,val)

    def deleteAtIndex(self,index):
        if 0 <= index <len(self.linkedlist):
            del self.linkedlist[index]

双指针技巧

基本概念

让我们从一个经典问题开始:

给定一个链表,判断链表中是否有环。

你可能已经使用哈希表提出了解决方案。但是,使用双指针技巧有一个更有效的解决方案。在阅读接下来的内容之前,试着自己仔细考虑一下。

想象一下,有两个速度不同的跑步者。如果他们在直路上行驶,快跑者将首先到达目的地。但是,如果它们在圆形跑道上跑步,那么快跑者如果继续跑步就会追上慢跑者。

扫描二维码关注公众号,回复: 9028808 查看本文章

这正是我们在链表中使用两个速度不同的指针时会遇到的情况:

如果没有环,快指针将停在链表的末尾。
如果有环,快指针最终将与慢指针相遇。

所以剩下的问题是:

这两个指针的适当速度应该是多少?

一个安全的选择是每次移动慢指针一步,而移动快指针两步。每一次迭代,快速指针将额外移动一步。如果环的长度为 M,经过 M 次迭代后,快指针肯定会多绕环一周,并赶上慢指针。

那其他选择呢?它们有用吗?它们会更高效吗?

环形链表

在这里插入图片描述
在这里插入图片描述

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


class Solution(object):
    def detectCycle(self,head):
        if head is None or head.next is None or head.next.next is None:
            return None
        fast = head.next
        slow = head.next.next
        while fast != slow:
            if slow.next == None or slow.next.next == None:
                return None
            fast = fast.next
            slow = slow.next.next
        slow = head
        while fast != slow:
            fast = fast.next
            slow = slow.next
        return fast

相交链表

在这里插入图片描述
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3

输出:Reference of the node with value = 8

输入解释:相交节点的值为 8 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。

在这里插入图片描述
输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1

输出:Reference of the node with value = 2

输入解释:相交节点的值为 2 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。

在这里插入图片描述
输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2

输出:null

输入解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
解释:这两个链表不相交,因此返回 null。

方法一:

class Solution(object):
    def getIntersectionNode(self, headA, headB):
        """
        :type head1, head1: ListNode
        :rtype: ListNode
        """
        p,q = headA,headB
        countA = countB = 0
        while p != None:
            p = p.next
            countA += 1
            
        while q != None:
            q = q.next
            countB += 1
            
        m,n = headA,headB
        if countA > countB:
            for i in range(countA - countB):
                m = m.next
                
        else:
            for i in range(countB - countA):
                n = n.next
        while m != n:
            m = m.next
            n = n.next
            
        return m

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

在这里插入图片描述

经典问题(反转链表)

基本概念

让我们从一个经典问题开始:

反转一个单链表。

一种解决方案是按原始顺序迭代结点,并将它们逐个移动到列表的头部。似乎很难理解。我们先用一个例子来说明我们的算法。

在这里插入图片描述
在这里插入图片描述

Python 实现

反转链表

反转一个单链表。

示例:

输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL

进阶:
你可以迭代或递归地反转链表。你能否用两种方法解决这道题?

class Solution(object):
    def reverseList(self,head):
        if head is None or head.next is None:
            return head
        p = head
        d = {}
        i = 0
        while p:
            d[i] = p
            p = p.next
            i += 1
        l = len(d)
        for i in range(l-1,0,-1):
            d[i].next = d[i-1]

        d[0].next = None
        return d[l-1]

解法二:

class Solution(object):
    def reverseList(self,head):
        if head is None:
            return None
        cur = head
        pre = None
        nxt = cur.next
        while nxt:
            cur.next = pre
            pre = cur
            cur = nxt
            nxt = nxt.next
        cur.next = pre
        head = cur
        return head

移除链表元素

删除链表中等于给定值 val 的所有节点。

示例:

输入: 1->2->6->3->4->5->6, val = 6
输出: 1->2->3->4->5
class Solution(object):
    def removeElements(self, head, val):
        """
        :type head: ListNode
        :type val: int
        :rtype: ListNode
        """
        if head:
            while head.val == val:
                head = head.next
                if head is None:
                    return head
            first = head
            second = first.next
            while second:
                if second.val == val:
                    first.next = second.next
                else:
                    first = first.next
                second = second.next
        return head

奇偶链表

给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。

请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。

示例 1:

输入: 1->2->3->4->5->NULL
输出: 1->3->5->2->4->NULL

示例 2:

输入: 2->1->3->5->6->4->7->NULL 
输出: 2->3->6->7->1->5->4->NULL

说明:

应当保持奇数节点和偶数节点的相对顺序。
链表的第一个节点视为奇数节点,第二个节点视为偶数节点,以此类推。

回文链表

请判断一个链表是否为回文链表。

示例 1:

输入: 1->2
输出: false
示例 2:

输入: 1->2->2->1
输出: true
进阶:
你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?

class Solution(object):
    def isPalindrome(self,head):
        if head is None or head.next is None:
            return True
        l = []
        p = head
        while p.next:
            l.append(p.val)
            p = p.next

        l.append(p.val)
        return l == l

小结 - 链表经典问题

我们为你提供了几个练习。你可能已经注意到它们之间的相似之处了。这里我们提供一些提示:

  1. 通过一些测试用例可以节省您的时间。

使用链表时不易调试。因此,在编写代码之前,自己尝试几个不同的示例来验证您的算法总是很有用的。

  1. 你可以同时使用多个指针。

有时,当你为链表问题设计算法时,可能需要同时跟踪多个结点。您应该记住需要跟踪哪些结点,并且可以自由地使用几个不同的结点指针来同时跟踪这些结点。

如果你使用多个指针,最好为它们指定适当的名称,以防将来必须调试或检查代码。

  1. 在许多情况下,你需要跟踪当前结点的前一个结点。

你无法追溯单链表中的前一个结点。因此,您不仅要存储当前结点,还要存储前一个结点。这在双链表中是不同的,我们将在后面的章节中介绍。

双链表

基本概念

我们在前面的章节中介绍了单链表。

单链接列表中的结点具有 Value 字段,以及用于顺序链接结点的“Next”引用字段。

在本文中,我们将介绍另一种类型的链表:双链表。

定义
双链表以类似的方式工作,但还有一个引用字段,称为“prev”字段。有了这个额外的字段,您就能够知道当前结点的前一个结点。

让我们看一个例子:
在这里插入图片描述

操作
与单链表类似,我们将介绍在双链表中如何访问数据、插入新结点或删除现有结点。

我们可以与单链表相同的方式访问数据:

我们不能在常量级的时间内访问随机位置。
我们必须从头部遍历才能得到我们想要的第一个结点。
在最坏的情况下,时间复杂度将是 O(N),其中 N 是链表的长度。
对于添加和删除,会稍微复杂一些,因为我们还需要处理“prev”字段。在接下来的两篇文章中,我们将介绍这两个操作

之后,我们提供练习,让你使用双链表重新设计链表。

添加操作 - 双链表

在这里插入图片描述
示例
在这里插入图片描述

删除操作 - 双链表

如果我们想从双链表中删除一个现有的结点 cur,我们可以简单地将它的前一个结点 prev 与下一个结点 next 链接起来。

与单链表不同,使用“prev”字段可以很容易地在常量时间内获得前一个结点。

因为我们不再需要遍历链表来获取前一个结点,所以时间和空间复杂度都是O(1)。

示例
在这里插入图片描述

小结 - 链表

基本概念

复习
让我们简要回顾一下单链表和双链表的表现。

它们在许多操作中是相似的。

它们都无法在常量时间内随机访问数据。
它们都能够在 O(1) 时间内在给定结点之后或列表开头添加一个新结点。
它们都能够在 O(1) 时间内删除第一个结点。
但是删除给定结点(包括最后一个结点)时略有不同。

在单链表中,它无法获取给定结点的前一个结点,因此在删除给定结点之前我们必须花费 O(N) 时间来找出前一结点。
在双链表中,这会更容易,因为我们可以使用“prev”引用字段获取前一个结点。因此我们可以在 O(1) 时间内删除给定结点。

对照
在这里插入图片描述
经过这次比较,我们不难得出结论:

如果你需要经常添加或删除结点,链表可能是一个不错的选择。

如果你需要经常按索引访问元素,数组可能是比链表更好的选择。

Python实现

合并两个有序链表

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

示例:

输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
class ListNode(object):
    def __init__(self,x):
        self.val = x
        self.next = None

class Solution(object):
    def mergeTwoLists(self,l1,l2):
        if l1 == None:
            return l2
        elif l2 == None:
            return l1
        if l1.val<l2.val:
            l1.next = self.mergeTwoLists(l1.next,l2)
            return l1
        else:
            l2.next = self.mergeTwoLists(l2.next,l1)
            return l2

两数相加

给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。

如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。

您可以假设除了数字 0 之外,这两个数都不会以 0 开头。

示例:

输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807
class ListNode(object):
    def __init__(self, x):
        self.val = x
        self.next = None

class Solution(object):
    def addTwoNumbers(self, l1, l2):
        """
        :type l1: ListNode
        :type l2: ListNode
        :rtype: ListNode
        """
        carry=0
        head=ListNode(0)
        node=head
        while l1 and l2:
            value=(l1.val+l2.val+carry)%10
            carry=(l1.val+l2.val+carry)//10
            l1.val=value
            node.next=l1
            node=node.next
            l1=l1.next
            l2=l2.next
        while l1:
            value=(l1.val+carry)%10
            carry=(l1.val+carry)//10
            l1.val=value
            node.next=l1
            node=node.next
            l1=l1.next
        while l2:
            value=(l2.val+carry)%10
            carry=(l2.val+carry)//10
            l2.val=value
            node.next=l2
            node=node.next
            l2=l2.next
        if carry:
            node.next=ListNode(1)
        return head.next

旋转链表

给定一个链表,旋转链表,将链表每个节点向右移动 k 个位置,其中 k 是非负数。

示例 1:

输入: 1->2->3->4->5->NULL, k = 2
输出: 4->5->1->2->3->NULL
解释:
向右旋转 1 步: 5->1->2->3->4->NULL
向右旋转 2 步: 4->5->1->2->3->NULL
示例 2:

输入: 0->1->2->NULL, k = 4
输出: 2->0->1->NULL
解释:
向右旋转 1 步: 2->0->1->NULL
向右旋转 2 步: 1->2->0->NULL
向右旋转 3 步: 0->1->2->NULL
向右旋转 4 步: 2->0->1->NULL

算法过程

1)定义2个指针,一快一慢

2)快的先走k步,慢的不动

3)接着快慢一起动,当快的走到节点的时候,慢的的下一项就是新的头部节点

class Solution(object):
    def rotateRight(self,head,k):
        if not head or not head.next or k == 0:
            return head
        ListLen = 0
        p = head
        #数有几个节点
        while(p):
            ListLen+=1
            p=p.next

        #优化
        k = k%ListLen
        if k == 0:
            return head

        #快节点先走k步
        p = head
        while(k>0):
            k-=1
            p=p.next

        slow = head
        fast = p

        #接着让fast走到最后一个节点,slow与它有相同的速度
        while fast.next:
            slow = slow.next
            fast = fast.next

        new_head = slow.next
        fast.next = head
        slow.next = None
        return new_head

扁平化多级双向链表

复制带随机指针的链表

发布了64 篇原创文章 · 获赞 9 · 访问量 4346

猜你喜欢

转载自blog.csdn.net/Avery123123/article/details/103506392