Python 触“类”旁通3|单链表基本操作之找查、删除和反转

本文详细介绍了单链表的各种操作,包括查找元素(首个、所有、指定索引)、删除元素(首节点、指定数量、指定值、倒数指定位置)、反转链表(整体、子链表)以及实战应用(删除倒数节点、成对反转、分组、旋转)。通过这些操作展示了链表数据结构的灵活运用。
部署运行你感兴趣的模型镜像

本篇准备学习单链表的找查、删除和反转操作,所有操作会用到以下前文《触“类”旁通2|数据结构入门之单链表的创建和遍历》中的代码,除__init__是必须的外,其它方法和属性基本只是为了方便赋值和检验结果正确与否而设,在解决问题时尽可能地不使用;更不能使用上篇中特别指出的“万能偷懒大法”来解决问题。节点类的基本属性和方法,如下:

class Node():
    def __init__(self, value=None, Next=None):
        self.val = value
        self.next = Next
        if type(self.next)==Node and self.next.val==None:
            self.next=None

    def __repr__(self):
        return f'Node({self.val}->{self.next})'
    
    def __str__(self):
        return f'{self.val}->{self.next}'

    def __len__(self):
        if self.val is None:
            return 0
        length,ptr = 0,self
        while ptr is not None:
            length += 1
            ptr = ptr.next
        return length 

    def __eq__(self, other):
        ptr1,ptr2 = self,other
        if len(ptr1)!=len(ptr2):
            return False
        while ptr1 is not None:
            if ptr1.val!=ptr2.val:
                return False
            ptr1 = ptr1.next
            ptr2 = ptr2.next
        else:
            return True

    def size(self):
        return self.__len__()

    @property
    def length(self):
        return self.size()

    @property
    def value(self):
        return self.val

    @property
    def values(self):
        ret,ptr = [],self
        while ptr is not None:
            ret.append(ptr.val)
            ptr = ptr.next
        return ret

    def pprint(self):
        items = [str(i) for i in self.values]
        if items==[]:
            print('None->None')
        elif len(items)==1:
            print(f'{items[0]}->None')
        else:
            print('->'.join(items)+'->None')

    def isNode(node):
        return isinstance(node,Node) and isinstance(node.next,(Node,type(None)))

    def build(*data, split=True):
        '''把数据转换成节点链表'''
        lst,ret = [],Node()
        for val in data:
            if type(val) is str:
                if not split:
                    lst.append(val)
                    continue
                if str=='':
                    continue
                try:
                    num = int(val)
                    lst.extend([int(_) for _ in val])
                except:
                    lst.extend([_ for _ in val])
            elif hasattr(val,'__iter__'):
                lst.extend([_ for _ in val])
            elif type(val) is Node:
                if val is not None:
                    lst.extend(val.values)
            else:
                lst.append(val)
        ret = Node()
        for i in lst[::-1]:
            ret = Node(i, ret)
        return ret

    def copy(node):
        ret = Node()
        ptr1,ptr2 = node,ret
        while ptr1 is not None:
            ptr2.next = Node(ptr1.val)
            ptr1,ptr2 = ptr1.next,ptr2.next
        return ret.next

1. 查找操作

1. 查找首个元素

返回数据域等于目标值num的第一个节点的索引号,头节点为0;找不到则返回 -1。

    def find(self, num):
        ptr,ret = self,-1
        while ptr is not None:
            ret += 1
            if ptr.val == num: break
            ptr = ptr.next
        else:
            ret = -1
        return ret

2. 查找所有元素

返回数据域等于目标值num的所有节点的索引号列表;找不到则返回 [-1]。

    def findall(self, num):
        ptr,idx,ret = self,-1,[]
        while ptr is not None:
            idx += 1
            if ptr.val == num:
                ret.append(idx)
            ptr = ptr.next
        if ret == []: ret = [-1]
        return ret

3. 返回指定索引号的元素

    def index(self, n):
        if type(n) is not int or n<0:
            raise BaseException('N = 0, 1, 2, ..., Node.size()-1')
        ptr,count = self,-1
        while ptr is not None:
            count += 1
            if count==n: return ptr.val
            ptr = ptr.next
        if ptr is None: self.index(-1)
            
'''
>>> node.index(0)
1
>>> node.index(2)
3
>>> node.index(4)
5
>>> node.index(5)
Traceback (most recent call last):
  File "<pyshell#27>", line 1, in <module>
    node.index(5)
  File "D:\Node0806.py", line 362, in index
    if ptr is None: self.index(-1)
  File "D:\Node0806.py", line 356, in index
    raise BaseException('N = 0, 1, 2, ..., Node.size()-1')
BaseException: N = 0, 1, 2, ..., Node.size()-1
>>>
'''

 4. 找出链表最大(小)值

    def nlargest(self):
        ptr,ret = self,self.val
        while ptr is not None:
            ret = max(ret, ptr.val)
            ptr = ptr.next
        return ret

    def nsmallest(self):
        ptr,ret = self,self.val
        while ptr is not None:
            ret = min(ret, ptr.val)
            ptr = ptr.next
        return ret

'''
>>> node = Node.build(11,3,12,5,6,7,23,8)
>>> node.nsmallest()
3
>>> node.nlargest()
23
>>>
'''

5. 返回子链表

返回包括第m到第n个元素的子链表,其中m,n 是个数非索引值,范围为 1 ≤ m ≤ n;为方便使用设置了一个负值,仅当n=-1时表示子链表从m开始截止到尾节点结束。即 m,n 可以用(1,n) 表示返回链表的前 n 个节点,而用(m,-1)表示返回链接从第m个节点开始到尾节点结束。

    def listMtoN(self, m, n):
        '''返回链表的从第m到n的节点,设置n=-1到尾节点'''
        if n!=-1 and n<m or m<1:
            raise BaseException('range: 1 <= M <= N')
        ret = Node()
        ptr,ptr1 = self,ret
        for _ in range(m-1):
            ptr = ptr.next
        while ptr is not None:
            m += 1
            if n!=-1 and m-n>1: break
            ptr1.next = Node(ptr.val)
            ptr,ptr1 = ptr.next,ptr1.next
        return ret.next


'''
>>> node = Node.build(range(1,11))
>>> node.listMtoN(1,4)
Node(1->2->3->4->None)
>>> node.listMtoN(3,8)
Node(3->4->5->6->7->8->None)
>>> node.listMtoN(5,-1)
Node(5->6->7->8->9->10->None)
>>> node.listMtoN(5,-1).reverse()
Node(10->9->8->7->6->5->None)
>>> node
Node(1->2->3->4->5->6->7->8->9->10->None)
>>> 
'''

2. 删除操作

1. 清空

>>> def clear(self):
	self.val = self.next = None
	return self

>>> n = Node.build(1,2,3)
>>> n
Node(1->2->3->None)
>>> clear(n)
Node(None->None)
>>> n
Node(None->None)
>>> 

2. 销毁

设置一个类的内置__del__() 方法:当节点不再被需要调用时,引用计数为0时就会被回收,并且调用__del__()方法显示其回收过程。

【知识点】Python 采用自动引用计数(简称 ARC)的方式实现垃圾回收机制。该方法的核心思想是:每个 Python 对象都会配置一个计数器,初始 Python 实例对象的计数器值都为 0,如果有变量引用该实例对象,其计数器的值会加 1,依次类推;反之,每当一个变量取消对该实例对象的引用,计数器会减 1。如果一个 Python 对象的的计数器值为 0,则表明没有变量引用该 Python 对象,即证明程序不再需要它,此时 Python 就会自动调用 __del__() 方法将其回收。 

del obj 并不主动调用__del__方法,只有引用计数为0时,__del__()才会被执行,并且定义了__del_()的实例无法被Python的循环垃圾收集器收集,所以尽量不要自定义__del__()。一般情况下,__del__() 不会破坏垃圾处理器。

节点类中添加以下代码用以测试: 

    def __del__(self):
        print(id(self),self)

    def init1():
        a=Node(1)
        b=Node(2)
        return a

    def init2():
        a=Node(1)
        b=Node(2)
        a.next = b
        return a

测试结果大致反馈了被回收对象的id和内容:

>>> n = Node.init1()
55624680 2->None        # init1()中的b没被调用,引用计数为0,即被回收
>>> n = Node.init2()
50818736 1->None        # init1()中的a也不需要了,才被回收
>>> n = Node()
55624680 1->2->None        # 这是init2()中的a,此时才被回收
55624752 2->None        # 这是init2()中的b被回收
>>> n = Node.build(3,4,5)
55624680 None->None        # 前2个是build方法中用过的两个临时空节点 ret
55624872 None->None
50818736 None->None        # 这个是上一步 n=Node() 设置的空节点
>>> del n
55624704 3->4->5->None        # del操作即可销毁,引用计数为0从头节点开始回收
55624872 4->5->None
55624920 5->None
>>> 

3. 删除节点

3-1.删除首节点

    def delhead(self):
        '''删除第一个节点'''
        if self.next is None:
            self.val = None
        else:
            self.val,self.next = self.next.val,self.next.next
        return self

注:首节点即前面的第一个节点,而头节点是专用名词 

3-2. 删除前n个节点

    def delfront(self,n):
        '''删除前n个节点'''
        for _ in range(n):
            if self.next is None:
                self.val = None
            else:
                self.val,self.next = self.next.val,self.next.next
        return self

3-3. 删除中间单个节点

    def delKthNode(self,k):
        '''删除第k个节点,首节点时k=1'''
        if type(k) is not int or k<1:
            raise BaseException('K = 1, 2, 3, ...')
        ret,tmp = Node(),Node()
        ptr1,ptr2 = self,tmp
        for _ in range(1,k):
            ptr2.next = Node(ptr1.val)
            ptr1,ptr2 = ptr1.next,ptr2.next
            if ptr1 is None:
                raise BaseException('length of Node less than K')
        if ptr1.next is not None:
            ret.val,ret.next = ptr1.next.val,ptr1.next.next
        if ret.val is not None:
            ptr2.next = ret
        self.val,self.next = tmp.next.val,tmp.next.next
        return self

注:如果要删除中间 n 个节点,上面代码中的ptr1往后多移n-1个节点即可;或者就用循环执行n次本函数。

3-4. 删除首个指定值的节点

即查找出指定值的索引号,然后删除。先idx=find(num)再delKthNode(idx+1);或者把两个函数综合到一个函数,比如 delAnum(self,num):

>>> node = Node.build(1,2,3,4,2,5)
>>> n = 2
>>> node
Node(1->2->3->4->2->5->None)
>>> node.delKthNode(node.find(n)+1)
Node(1->3->4->2->5->None)
>>> node
Node(1->3->4->2->5->None)
>>> 

3-5. 删除指定值的所有节点

>>> def delall(node,n):
	while node.find(n)!=-1:
		node.delKthNode(node.find(n)+1)
	return node

>>> node = Node.build(1,2,3,4,2,2,5,2)
>>> node
Node(1->2->3->4->2->2->5->2->None)
>>> delall(node,2)
Node(1->3->4->5->None)
>>> node
Node(1->3->4->5->None)
>>>
>>> # 从副本中删除:
>>> node = Node.build(1,2,3,4,2,2,5,2)
>>> delall(node.copy(),2)
Node(1->3->4->5->None)
>>> node
Node(1->2->3->4->2->2->5->2->None)
>>> 

3-6. 弹出首、尾节点

3.3中的函数,当k等于节点链长度时,即删除尾节点。但在此我们要模仿列表的pop()方法,删除尾部元素的同时,返回它的值:

>>> lst = [1,2,3]
>>> lst.pop()
3
>>> lst
[1, 2]
>>> lst.pop()
2
>>> lst
[1]
>>> lst.pop()
1
>>> lst
[]
>>> lst.pop()
Traceback (most recent call last):
  File "<pyshell#330>", line 1, in <module>
    lst.pop()
IndexError: pop from empty list

>>>  

代码及测试效果如下:

    def pop(self):
        '''弹出尾节点,并返回它的值'''
        if self.val==None==self.next:
            raise IndexError('pop from empty Node')
        if self.next is None:
            ret,self.val = self.val,None
            return ret
        temp = Node()
        ptr1,ptr2 = self,temp
        while ptr1.next is not None:
            ptr2.next = Node(ptr1.val)
            ptr1,ptr2 = ptr1.next,ptr2.next
            ret = ptr1.val
        self.val,self.next = temp.next.val,temp.next.next
        return ret


'''
>>> a=Node.build(1,2)
>>> a.pop()
2
>>> a
Node(1->None)
>>> a.pop()
1
>>> a
Node(None->None)
>>> a.pop()
Traceback (most recent call last):
  File "<pyshell#341>", line 1, in <module>
    a.pop()
  File "D:\Node0806.py", line 103, in pop
    raise IndexError('pop from empty Node')
IndexError: pop from empty Node
>>> 
'''

同理,如要删除首节点的同时返回它的值;只要把delfront()中的返回return语句修改一下:

    def frontpop(self):
        '''删除第一个节点,并返回它的值'''
        if self.val==None==self.next:
            raise IndexError('frontpop from empty Node')
        ret = self.val
        if self.next is None:
            self.val = None
        else:
            self.val,self.next = self.next.val,self.next.next
        return ret

Node.pop()的应用

把链表各节点的值依次从尾部弹出,存入新链表刚好形成反序的效果:

    def reverseList(self):
        ptr = ret = Node()
        while True:
            try:
                num = self.pop()
            except:
                break
            ptr.next = Node(num)
            ptr = ptr.next
        return ret.next

'''
>>> node = Node.build(1,2,3,4,5)
>>> node
Node(1->2->3->4->5->None)
>>> node.reverseList()
Node(5->4->3->2->1->None)
>>> node
Node(None->None)    # node 已被弹空
>>> 

>>> # 想要节点本身倒序,直接赋值
>>> node = Node.build(1,2,3,4,5)
>>> node = node.reverseList()
>>> node
Node(5->4->3->2->1->None)
>>> 
'''

注:以上只为测试而已没有实用性,因为对有N个节点的链表来说: 单次弹出 fontpop()的效率是 pop() 的N倍;而全部弹出的效率是(N+1)/2倍。所以采用前弹加前插法的效率更高一些。
 

3. 反转操作

反转即把链表各节点的指向倒转,头尾反过来;类的内置方法__reversed__() 重载了python的内建函数 reversed();而.reverse()直接对节点操作:

1. 重载reversed()函数

    def __reversed__(self):
        ret,ptr = Node(),self
        while ptr is not None:
            ret = Node(ptr.val, ret)
            ptr = ptr.next
        return ret

另一种写法:

    def reversed(self):
        ret,ptr = None,self.copy()
        while ptr is not None:
            tmp,ptr.next = ptr.next,ret
            ret,ptr = ptr,tmp
        return ret

注: 第一行中ret = None,不是Node();另外还用了self的副本.copy()进行操作,否则self本身会被改动。不要.copy()的话,多做一次赋值也是可以的: node = node.reversed() 。

2. 直接反转节点.reverse()

    def reverse(self):
        ptr,ret = self,Node()
        while ptr is not None:
            ret = Node(ptr.val,ret)
            ptr = ptr.next
        self.val,self.next = ret.val,ret.next
        return self

3. 两者的区别

区别在于reversed(node)不改变被反转节点node,只是返加一个反序的链表;node.reverse()直接改变了node的节点顺序。差不多与列表中的sorted()和.sort()的区别一致。

>>> node = Node.build(1,2,3,4,5)
>>> reversed(node)
Node(5->4->3->2->1->None)
>>> node
Node(1->2->3->4->5->None)
>>> node.reverse()
Node(5->4->3->2->1->None)
>>> node
Node(5->4->3->2->1->None)
>>> 


4. 力扣实战题

实战一:删除倒数第N个节点

Remove Nth Node From End of List (#19)
Given a linked list, remove the n-th node from the end of list and return its head.

示例

Given linked list: 1->2->3->4->5, and n = 2.
After removing the second node from the end, the linked list becomes 1->2->3->5.

输入: 1->2->3->4->5, and n = 2.
输出: 1->2->3->5.

解法一:本文3.3-4. 中的 delKthNode() 是删除前面数起第k个,那么倒数第N个只要先遍历一个总个数,然后两数一减后+1再调用即可:

>>> node = Node.build(range(1,6))
>>> node
Node(1->2->3->4->5->None)
>>> n = 2
>>> node.delKthNode(node.length - n + 1)
Node(1->2->3->5->None)
>>> 

综合到一起的代码:

    def delNthEnd(self,n):
        '''删除倒数第n个节点,尾节点时n=1'''
        if type(n) is not int or n<1:
            raise BaseException('N = 1, 2, 3, ...')
        ptr,size = self,0
        while ptr is not None:
            size += 1
            ptr = ptr.next
        k = size - n + 1
        if k<1:
            raise BaseException('length of Node less than N')
        ret,tmp = Node(),Node()
        ptr1,ptr2 = self,tmp
        for _ in range(1,k):
            ptr2.next = Node(ptr1.val)
            ptr1,ptr2 = ptr1.next,ptr2.next
        if ptr1.next is not None:
            ret.val,ret.next = ptr1.next.val,ptr1.next.next
        if ret.val is not None:
            ptr2.next = ret
        self.val,self.next = tmp.next.val,tmp.next.next
        return self

测试效果:

>>> node = Node.build(range(1,6))
>>> node.delNthEnd(2)
Node(1->2->3->5->None)
>>> node = Node.build(range(1,6))
>>> node.delKthNode(4)  # 倒数2和前数4删的效果一致
Node(1->2->3->5->None)
>>>

>>> node = Node.build(range(1,6))
>>> node.delNthEnd(1)
Node(1->2->3->4->None)
>>> node = Node.build(range(1,6))
>>> node.delNthEnd(3)
Node(1->2->4->5->None)
>>> node = Node.build(range(1,6))
>>> node.delNthEnd(5)
Node(2->3->4->5->None)
>>> node = Node.build(range(1,6))
>>> node.delNthEnd(6)
Traceback (most recent call last):
  File "<pyshell#115>", line 1, in <module>
    node.delNthEnd(66)
  File "D:\Node0806.py", line 259, in delNthEnd
    raise BaseException('length of Node less than N')
BaseException: length of Node less than N

解法二:这个方法相对简单一些,设置快慢两个指针不用先遍历出链表长度。原理:找倒数第n个节点,先用“快指针”移动n个节点,然后“快慢”两个指针同时步进;当快指针到达尾节点时,慢指针刚好到达倒数第n个节点。

    def delEndTwoPtr(self,n):
        '''快慢双指针删除倒数第n个节点,尾节点时n=1'''
        if type(n) is not int or n<1:
            raise BaseException('N = 1, 2, 3, ...')
        fast,slow = self,self
        ret = Node()
        ptr = ret
        while fast is not None:
            fast = fast.next
            n -= 1
            if n==0: break
        if n>0:
            raise BaseException('length of Node less than N')
        while fast is not None:
            ptr.next = Node(slow.val)
            ptr = ptr.next
            fast,slow = fast.next,slow.next
        ptr.next = slow.next
        self.val,self.next = ret.next.val,ret.next.next
        return self

实战二:成对反转节点

Swap Nodes in Pairs (#24)
Given a linked list, swap every two adjacent nodes and return its head.
You may not modify the values in the list's nodes, only nodes itself may be changed.

示例

Given 1->2->3->4, you should return the list as 2->1->4->3.

输入: 1->2->3->4.
输出: 2->1->4->3.

要求不能修改节点的数据域,假设或有成单的尾节点不反转。

解法:前后相邻的双指针双步进遍历链表即可。

    def swapPairs(self):
        if self.next is None:
            return self
        ptr1,ptr2 = self,self
        ret = Node()
        ptr,ptr1 = ret,ptr1.next
        while ptr1 is not None:
            ptr.next = Node(ptr1.val)
            ptr = ptr.next
            ptr.next = Node(ptr2.val)
            ptr = ptr.next
            ptr1,ptr2 = ptr1.next,ptr2.next
            if ptr1 is None: break 
            if ptr1.next is None:
                ptr.next = Node(ptr1.val)
            ptr1,ptr2 = ptr1.next,ptr2.next
        self.val,self.next = ret.next.val,ret.next.next
        return self


'''
>>> node = Node.build(range(1,5)); node.swapPairs()
Node(2->1->4->3->None)
>>> node = Node.build(range(1,6)); node.swapPairs()
Node(2->1->4->3->5->None)
>>> node = Node.build(range(1,7)); node.swapPairs()
Node(2->1->4->3->6->5->None)
>>> node = Node.build(range(1,8)); node.swapPairs()
Node(2->1->4->3->6->5->7->None)
>>>
'''

实战三:成组反转节点

Reverse Nodes in k-Group(#25)
Given a linked list, reverse the nodes of a linked list k at a time and return its modified list.
k is a positive integer and is less than or equal to the length of the linked list. If the number of nodes is not a multiple of k then left-out nodes in the end should remain as it is.      

示例

Given this linked list: 1->2->3->4->5
For k = 2, you should return: 2->1->4->3->5
For k = 3, you should return: 3->2->1->4->5

输入: 1->2->3->4->5.
输出: k=2时,2->1->4->3->5;
    k=3时,3->2->1->4->5.

如节点的数量不是k的倍数,那么最后剩下的个数小于k的则保持原样不反转。

本题就是上一题的加强版,当k=2时两题的效果相同。

解法:因为k值可以很大,不能像上题一样交换位置;应该多次读出k个节点,分别反转后相接;若最后剩余的少于k个节点就原样追加。反转部分就调用上面重载好的reversed()函数即可。

    def reverseKGroup(self, k):
        if type(k) is not int or k<1:
            raise BaseException('K = 1, 2, 3, ...')
        if k==1: return self
        ret = Node()
        ptr,ptr1 = self,ret
        size = 0
        while True:
            kgroup = Node()
            ptr2 = kgroup
            count = 0
            for _ in range(k):
                size += 1
                if ptr is None:
                    if k>=size:
                        raise BaseException('length of Node less than K')
                    break
                ptr2.next = Node(ptr.val)
                count += 1
                ptr,ptr2 = ptr.next,ptr2.next
            kgroup = kgroup.next
            if count==k:
                kgroup.reverse()
                ptr2 = kgroup
                for _ in range(k):
                    ptr1.next = Node(ptr2.val)
                    ptr1,ptr2 = ptr1.next,ptr2.next
            else:
                ptr1.next = kgroup
                break
        self.val,self.next = ret.next.val,ret.next.next
        return self



'''
>>> node = Node.build(range(1,6)); node.reverseKGroup(2)
Node(2->1->4->3->5->None)
>>> node = Node.build(range(1,6)); node.reverseKGroup(3)
Node(3->2->1->4->5->None)
>>> 
>>> node = Node.build(range(1,11)); node.reverseKGroup(2)
Node(2->1->4->3->6->5->8->7->10->9->None)
>>> node = Node.build(range(1,11)); node.reverseKGroup(3)
Node(3->2->1->6->5->4->9->8->7->10->None)
>>> node = Node.build(range(1,11)); node.reverseKGroup(4)
Node(4->3->2->1->8->7->6->5->9->10->None)
>>> node = Node.build(range(1,11)); node.reverseKGroup(5)
Node(5->4->3->2->1->10->9->8->7->6->None)
>>> node = Node.build(range(1,11)); node.reverseKGroup(10)
Node(10->9->8->7->6->5->4->3->2->1->None)
>>>
'''

实战四:反转链表中一段节点

Reverse Linked List (a part of NodeList)  (#92)

Reverse a linked list from position m to n. Do it in one-pass.
Note: 1 ≤ m ≤ n ≤ length of list.

示例

输入: 1->2->3->4->5->None, m = 2, n = 4
输出: 1->4->3->2->5->None

解法一:直接调用本文1-4.中的 listMtoN()以及3-1.的__reversed__()非常方便,按m,n的值取出链表的三段,最后拼接一下即可:

>>> node = Node.build(1,2,3,4,5)
>>> m,n = 2,4
>>> left = node.listMtoN(1,m-1)
>>> mid = node.listMtoN(m,n)
>>> right = node.listMtoN(n+1,-1)
>>>
>>> left,reversed(mid),right
(Node(1->None), Node(4->3->2->None), Node(5->None))
>>> 

综合后的代码: 

    def reverseMtoN(self, m, n):
        ret = Node()
        ptr,ptr1 = self,ret
        for _ in range(m-1):
            ptr1.next = Node(ptr.val)
            ptr,ptr1 = ptr.next,ptr1.next
        ptr1.next = reversed(self.listMtoN(m, n))
        ptr,ptr1 = self,ret
        for _ in range(n):
            ptr,ptr1 = ptr.next,ptr1.next      
        ptr1.next = ptr
        return ret.next

# 未对m,n的取值范围做判断
'''
>>> node = Node.build(1,2,3,4,5)
>>> node.reverseMtoN(2,4)
Node(1->4->3->2->5->None)
>>> node = Node.build(1,2,3,4,5,6,7,8)
>>> node.reverseMtoN(3,6)
Node(1->2->6->5->4->3->7->8->None)
>>> 
'''

解法二:不调用之前的函数,直接操作:遍历时左右两段用追加,中间一段(m到n)用前插法刚好完成倒转。

    def reverseMtoN(self, m, n):
        ret = Node()
        ptr,ptr1 = self,ret
        for _ in range(m-1):
            ptr1.next = Node(ptr.val)
            ptr,ptr1 = ptr.next,ptr1.next
        mid = Node(ptr.val)
        for _ in range(m,n):
            if ptr.next is None: break
            mid = Node(ptr.next.val,mid)
            ptr = ptr.next
        ptr1.next = mid
        while mid is not None:
            ptr1,mid = ptr1.next,mid.next
        while ptr.next is not None:
            ptr1.next = Node(ptr.next.val)
            ptr,ptr1 = ptr.next,ptr1.next
        return ret.next

实战五:链表分组

Partition List (#86)
Given a linked list and a value x, partition it such that all nodes less than x come before nodes greater than or equal to x.
You should preserve the original relative order of the nodes in each of the two partitions.

示例

输入: 1->4->3->2->5->2->None, x = 3
输出: 1->2->2->4->3->5->None

给定一个链表和一个整数,把链表分成“小于指定数”和“不小于指定数”的二组连接在一起,并且各组元素在本组中的先后位置保持与原链表相同。

解法一:只新建一个链表,原链表遍历两遍分别找出“小于的”和“不小于的”依次追加到新链表中。

解法二:新建两个链表,遍历一次就分出两组,然后把“不小于组”接到“小于组”的后面。

    def partitionI(self, x):
        '''把链表分成小于和不小于x的两组并相接'''
        ret = Node()
        ptr1,ptr2,ptr = self,self,ret
        while ptr1 is not None:
            if ptr1.val<x:
                ptr.next = Node(ptr1.val)
                ptr = ptr.next
            ptr1 = ptr1.next
        while ptr2 is not None:
            if ptr2.val>=x:
                ptr.next = Node(ptr2.val)
                ptr = ptr.next
            ptr2 = ptr2.next
        return ret.next

    def partitionII(self, x):
        '''把链表分成小于和不小于x的两组并相接'''
        ret,gex = Node(),Node()
        ptr1,ptr2,ptr = ret,gex,self
        while ptr is not None:
            if ptr.val<x:
                ptr1.next = Node(ptr.val)
                ptr1 = ptr1.next
            else:
                ptr2.next = Node(ptr.val)
                ptr2 = ptr2.next
            ptr = ptr.next
        ptr1.next = gex.next
        return ret.next


'''
>>> node = Node.build(1,4,3,2,5,2)
>>> node.partitionI(3)
Node(1->2->2->4->3->5->None)
>>> node
Node(1->4->3->2->5->2->None)
>>> node.partitionII(3)
Node(1->2->2->4->3->5->None)
>>> node
Node(1->4->3->2->5->2->None)
>>> 
>>> node.partitionI(2)
Node(1->4->3->2->5->2->None)
>>> node.partitionII(2)
Node(1->4->3->2->5->2->None)
>>>
>>> node.partitionI(4)
Node(1->3->2->2->4->5->None)
>>> node.partitionII(4)
Node(1->3->2->2->4->5->None)
>>> 
'''

类似的还有leetcode第328题是把链表分成索引号是奇、偶数的两组;当然还可以按节点的数据域是奇、偶数或者是质、合数......等等各种不同性质来分组。

实战六:链表旋转

Rotate List (#61)
Given a linked list, rotate the list to the right by k places, where k is non-negative.

示例1

输入: 1->2->3->4->5->NULL, k = 2
输出: 4->5->1->2->3->NULL
解释:
rotate 1 steps to the right: 5->1->2->3->4->NULL
rotate 2 steps to the right: 4->5->1->2->3->NULL

示例2

输入: 0->1->2->NULL, k = 4
输出: 2->0->1->NULL
解释:
rotate 1 steps to the right: 2->0->1->NULL
rotate 2 steps to the right: 1->2->0->NULL
rotate 3 steps to the right: 0->1->2->NULL
rotate 4 steps to the right: 2->0->1->NULL

解法一:链表向右移动,并把原尾节点放到原头节点之前,这是右旋转;顺带把反向的左旋转一并解决:即链表向左移动,并把原头节点放到原尾节点之后。

    def rotateR(self, k):
        '''链表向右旋转,步进数为k'''
        k %= self.length
        if k==0:
            return self
        elif k==1:
            ret = Node()
            ptr,ptr1 = self,ret
            while ptr.next is not None:
                ptr1.next = Node(ptr.val)
                ptr,ptr1 = ptr.next,ptr1.next
            tail = Node(ptr.val,ret.next)
            return tail
        else:
            for _ in range(k):
                self = self.rotateR(1)
            return self

    def rotateL(self, k):
        '''链表向左旋转,步进数为k'''
        k %= self.length
        if k==0:
            return self
        elif k==1:
            ret,head = Node(),Node(self.val)
            ptr,ptr1 = self,ret
            while ptr.next is not None:
                ptr1.next = Node(ptr.next.val)
                ptr,ptr1 = ptr.next,ptr1.next
            ptr1.next = head
            return ret.next
        else:
            for _ in range(k):
                self = self.rotateL(1)
            return self

'''
>>> a = Node.build(1,2,3,4,5);k=2
>>> a.rotateR(k)
Node(4->5->1->2->3->None)
>>> a.rotateL(k)
Node(3->4->5->1->2->None)
>>> 
>>> b = Node.build(0,1,2);k=4
>>> b.rotateR(k)
Node(2->0->1->None)
>>> b.rotateL(k)
Node(1->2->0->None)
>>> 
>>>
>>> a = Node.build(1,2,3,4,5);k=7
>>> for i in range(1,k+1):
	print(f'rotate {i} steps to the right: ',a.rotateR(i))

	
rotate 1 steps to the right:  5->1->2->3->4->None
rotate 2 steps to the right:  4->5->1->2->3->None
rotate 3 steps to the right:  3->4->5->1->2->None
rotate 4 steps to the right:  2->3->4->5->1->None
rotate 5 steps to the right:  1->2->3->4->5->None
rotate 6 steps to the right:  5->1->2->3->4->None
rotate 7 steps to the right:  4->5->1->2->3->None
>>>
>>> for i in range(1,k+1):
	print(f'rotate {i} steps to the left: ',a.rotateL(i))

	
rotate 1 steps to the left:  2->3->4->5->1->None
rotate 2 steps to the left:  3->4->5->1->2->None
rotate 3 steps to the left:  4->5->1->2->3->None
rotate 4 steps to the left:  5->1->2->3->4->None
rotate 5 steps to the left:  1->2->3->4->5->None
rotate 6 steps to the left:  2->3->4->5->1->None
rotate 7 steps to the left:  3->4->5->1->2->None
>>> 
'''

解法二:链表复制两份头尾相连,新链表的原链表长度 size 的每一段都是旋转到某一步的结果。并且 k和size 能整除即不移动,有余数就是移到的步数;同时k>0表示向左移,k<0表示向右移。

    def rotate(self, k):
        '''链表旋转|k|步,正数向左负数向右'''
        if k==0: return self
        dbouble,size = Node(self.val),0
        ptr,ptr1 = self,dbouble
        while ptr.next is not None:
            ptr1.next = Node(ptr.next.val)
            ptr,ptr1 = ptr.next,ptr1.next
        ptr = self
        while ptr is not None:
            size += 1
            ptr1.next = Node(ptr.val)
            ptr,ptr1 = ptr.next,ptr1.next
        k %= size
        if k==0: return self
        ret = Node()
        ptr,ptr1 = dbouble,ret
        for i in range(k+size):
            if i>=k:
                ptr1.next = Node(ptr.val)
                ptr1 = ptr1.next
            ptr = ptr.next
        return ret.next

'''        
>>> node = Node.build(1,2,3,4,5)
>>> for i in range(7):
	node.rotate(i)

	
Node(1->2->3->4->5->None)
Node(2->3->4->5->1->None)
Node(3->4->5->1->2->None)
Node(4->5->1->2->3->None)
Node(5->1->2->3->4->None)
Node(1->2->3->4->5->None)
Node(2->3->4->5->1->None)
>>> for i in range(7):
	node.rotate(-i)

	
Node(1->2->3->4->5->None)
Node(5->1->2->3->4->None)
Node(4->5->1->2->3->None)
Node(3->4->5->1->2->None)
Node(2->3->4->5->1->None)
Node(1->2->3->4->5->None)
Node(5->1->2->3->4->None)
>>>
'''

——*** 本篇完 ***——
 


相关文章链接:

触“类”旁通:以单链表为例,一步步深入了解类

触“类”旁通2|数据结构入门之单链表的创建和遍历

您可能感兴趣的与本文相关的镜像

Python3.8

Python3.8

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hann Yang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值