数据结构、算法刷题(5)单链表基本操作(python实现,小白)

单链表的基本技巧:

1、合并两个有序链表

这道题一般两种算法,一种是使用递归,每次的递归条件是:谁的头结点小,就把谁身后的数据以及另一个链表重新放入递归。结束条件是某一个链表为空。

非递归算法是重新找个空表,用p做表头,然后比较list1和list2的当前大小,将小的连接到p上,循环到某个表为空,就把另一个表剩余的都连接到p上。最后输出p表头之后的部分。

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 list1 is None:

             return list2

         elif list2 is None:

             return list1

         elif list1.val > list2.val:

             list2.next = self.mergeTwoLists(list1,list2.next)

             return list2

         else:

             list1.next = self.mergeTwoLists(list1.next,list2)

             return list1

        #非递归

        dummp = ListNode(-1)

        p = dummp

        p1 = list1

        p2 = list2

        while p1!=None and p2!=None:

            if p1.val > p2.val:

                p.next = p2

                p2 = p2.next

            else:

                p.next = p1

                p1 = p1.next

            p = p.next

        if p1 is None:

            p.next = p2

        if p2 is None:

            p.next = p1

        return dummp.next

2、合并k个有序链表

这里可以使用二叉堆的概念。(大根堆、小根堆)其主要操作就两个,sink(下沉)和swim(上浮),用以维护二叉堆的性质。其主要应用有两个,首先是一种排序方法「堆排序」,第二是一种很有用的数据结构「优先级队列」。二叉堆其实就是一种特殊的二叉树(完全二叉树),只不过存储在数组里。一般的链表二叉树,我们操作节点的指针,而在数组里,我们把数组索引作为指针。

把 arr[1] 作为整棵树的根的话,每个节点的父节点和左右孩子的索引都可以通过简单的运算得到,这就是二叉堆设计的一个巧妙之处。

二叉堆还分为最大堆和最小堆。最大堆的性质是:每个节点都大于等于它的两个子节点。类似的,最小堆的性质是:每个节点都小于等于它的子节点。

优先级队列这种数据结构有一个很有用的功能,你插入或者删除元素的时候,元素会自动排序,这底层的原理就是二叉堆的操作。

数据结构的功能无非增删查该,优先级队列有两个主要 API,分别是insert插入一个元素和delMax删除最大元素(如果底层用最小堆,那么就是delMin

为什么要有上浮 swim 和下沉 sink 的操作呢?为了维护堆结构。

我们要讲的是最大堆,每个节点都比它的两个子节点大,但是在插入元素和删除元素时,难免破坏堆的性质,这就需要通过这两个操作来恢复堆的性质了。

对于最大堆,会破坏堆性质的有有两种情况:

  1. 如果某个节点 A 比它的子节点(中的一个)小,那么 A 就不配做父节点,应该下去,下面那个更大的节点上来做父节点,这就是对 A 进行下沉

  2. 如果某个节点 A 比它的父节点大,那么 A 不应该做子节点,应该把父节点换下来,自己去做父节点,这就是对 A 的上浮

当然,错位的节点 A 可能要上浮(或下沉)很多次,才能到达正确的位置,恢复堆的性质。所以代码中肯定有一个while循环。

上代码(小根堆):

class Heapq:
    pq = [-1]#存放二叉堆元素的数组,一般情况下都不要下标为0的元素,所以提前把这个下标占了
    N = 0 #数组中的个数
    def parent(self,k):#输入下标k之后得到,得到k的父亲节点
        return int(k/2)
    def right(self,k):#输入下标k之后,得到k的右孩子
        return int(k*2+1)
    def left(self,k):#输入下标k之后,得到k的做孩子
        return int(k*2)
    def exch(self,a,b):#交换
        t = a
        a = b
        b = t
        return a , b
    def less(self,a,b):#比较
        if a < b:
            return True
        else:
            return False
    def swim(self,k,pq):

        #k上浮的条件:不能是根,而且要比它的父亲小
        while k>1 and self.less(pq[k],pq[self.parent(k)]):

        #交换值
            t = pq[k]
            pq[k] = pq[self.parent(k)]
            pq[self.parent(k)] = t

        #让下标移动到下一个比较的位置
            a,b = self.exch(k,self.parent(k))
            k = a

    def sink(self,k):
        while self.left(k) <= self.N: #k如果有左孩子,就有下沉的可能
            smaller = self.left(k)  #假设k的左孩子小
            if self.right(k)<=self.N and #k存在右孩子 self.less(self.pq[self.right(k)],self.pq[self.left(k)]): #而且右孩子比左孩子小
                smaller = self.right(k)  #将较小值换成右孩子
            if self.less(self.pq[smaller],self.pq[k]):  #较小的孩子比k小

                #交换值
                t = self.pq[smaller]
                self.pq[smaller] = self.pq[k]
                self.pq[k] = t

                #让下标移动到下一个比较位置
                a ,b =self.exch(k,smaller)
                k = a
            else:
                break  #比较小的孩子也比k大,就跳出循环


    def insert(self,data):#插入到堆底,然后swim
        self.N += 1
        self.pq.append(data)
        self.swim(self.N,self.pq)


    def delmin(self):#把堆顶和堆底互换,然后删除掉最后的节点,再sink堆顶
        t = self.pq[1]
        self.pq[1] = self.pq[self.N]
        self.pq[self.N] = t
        self.pq.pop()
        self.N -= 1
        self.sink(1)
        return t

因此上述题目的解题过程是:

class ListNode:

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

         self.val = val

         self.next = next

class Solution:

    def mergeKLists(self, lists: List[Optional[ListNode]]) -> Optional[ListNode]:

        #将所有链表插到小根堆中,然后执行delmin,将返回的值插入到新的链表中

        dummp = ListNode(-1)

        p = dummp

        h = Heapq()

        h.N = 0

        h.pq = [0]

        for i in range(len(lists)):

            while lists[i]:

                h.insert(lists[i].val)

                lists[i] = lists[i].next

        for i in range(h.N):

            t = h.delmin()

            p.next = ListNode(t)

            p = p.next

        return dummp.next

3、寻找单链表的倒数第k个节点

解题思路:

 

 

# 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]:

        p1 = head

        p2 = head

        for i in range(n):

            p1 = p1.next  #先让p1走n步

        if p1: #走完n步之后,要判断一下,此时的p1是否已经走到了链表的尽头

            while p1.next: #当p1的指向链表的尾端时,p2的next就是我们要删除的元素

                p1 = p1.next

                p2 = p2.next

            p3 = p2.next

            p2.next = p3.next #删除p2.next

        else: #如果走了n步之后, p1已经到了链表的尽头,说明要删除的元素是表头

            head = head.next

        return head

 

4、寻找单链表的中点

 

class Solution:

    def middleNode(self, head: Optional[ListNode]) -> Optional[ListNode]:

        fast = head

        slow = head

        while fast and fast.next:

            fast = fast.next.next

            slow = slow.next

        return slow

5、判断单链表是否包含环并找出环起点

如果要计算环的起点:

 

 

即相遇表示为有环,相遇之后,将其中一个重新指回head,再次相遇即为起点。

 

 

class Solution:

    def hasCycle(self, head: Optional[ListNode]) -> bool:

        fast = head

        slow = head

        while fast and fast.next: #进入循环后,如果没有环,就会在链表末退出

            fast = fast.next.next

            slow = slow.next

            if fast == slow: #如果有环,则两个指针会相遇,返回true

                return True

        return False

class Solution:

    def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:

        fast = head

        slow = head

        while fast and fast.next:

            fast = fast.next.next

            slow = slow.next

            if fast == slow: #如果有环

                slow = head #让其中一个指针重新指向头结点

                while fast != slow: #当两个指针未相遇时,按照步长一致走

                    fast = fast.next

                    slow = slow.next

                return slow #相遇后返回指针

        return None

6、判断两个单链表是否相交并找出交点

让两个指针分别遍历a-b,b-a,如果有相交节点,则两个指针会相遇。如果不想交,两个指针最后会走到null 

class Solution:

    def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> Optional[ListNode]:

        p1 = headA

        p2 = headB

        while p1 != p2: #在保证没有环的情况下,如果没有相交,最后都指向了null,有相交,则相等

            if p1 is None:

                p1 = headB

            else:

                p1 = p1.next

            if p2 is None:

                p2 = headA

            else:

                p2 = p2.next

        return p1

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值