【问题标题】:reverse a linked list in groups反向分组链表
【发布时间】:2021-01-07 20:37:59
【问题描述】:

我现在正在处理分组反转链表,但遇到了一些问题。

问题是:

给定一个具有“n”个节点的 LinkedList,根据它的大小按以下方式反转它:

如果“n”是偶数,则在一组 n/2 个节点中反转列表。如果 n 是奇数, 保持中间节点不变,反转前“n/2”个节点,然后 反转最后的“n/2”个节点。

我的做法是:

def lenLinkedlist(head):
    count = 0
    current = head
    while current:
        current = current.next
        count += 1
    return count

def reverseInGroupPart(head, n):
    count = 0
    previous, current, next = None, head, None
    while current and count < n//2:
        next = current.next
        current.next = previous
        previous = current
        current = next
        count += 1
    # even
    if n%2 == 0:
        # current at middle right now
        # head supports to be middle now
        head.next = reverseInGroupPart(current, n)
    # odd
    else:
        # current at middle now
        head.next = current
        current.next = reverseInGroupPart(current.next, n)
    return previous

def reverseGroups(head):
    n = lenLinkedlist(head)
    if n%2 == 0:
        return reverseInGroupPart(head, n)
    else:
        return reverseInGroupPart(head, n)

class Node:
    def __init__(self, _value, _next = None):
        self.value = _value
        self.next = _next
    def print_list(self):
        temp = self
        while temp:
            print(temp.value, end = ' ')
            temp = temp.next
        print()

def main():
    head = Node(1)
    head.next = Node(2)
    head.next.next = Node(3)
    head.next.next.next = Node(4)
    head.next.next.next.next = Node(5)
    head.next.next.next.next.next = Node(6)

    print('original linked list is: ', end = '')
    head.print_list()
    result = reverseGroups(head)
    print('reverse of linked list is ', end = '')
    result.print_list()

main()

有错误:

Traceback (most recent call last):
  File "/Users/PycharmProjects/tester/main.py", line 62, in <module>
    main()
  File "/Users/PycharmProjects/tester/main.py", line 58, in main
    result = reverseGroups(head)
  File "/Users/PycharmProjects/tester/main.py", line 33, in reverseGroups
    return reverseInGroupPart(head, n)
  File "/Users/PycharmProjects/tester/main.py", line 22, in reverseInGroupPart
    head.next = reverseInGroupPart(current, n)
  File "/Users/PycharmProjects/tester/main.py", line 22, in reverseInGroupPart
    head.next = reverseInGroupPart(current, n)
  File "/Users/PycharmProjects/tester/main.py", line 22, in reverseInGroupPart
    head.next = reverseInGroupPart(current, n)
  [Previous line repeated 993 more times]
  File "/Users/PycharmProjects/tester/main.py", line 19, in reverseInGroupPart
    if n%2 == 0:
RecursionError: maximum recursion depth exceeded in comparison
original linked list is: 1 2 3 4 5 6 

Process finished with exit code 1

我尝试使用递归方法解决问题,但不确定是什么导致了错误。谢谢。

【问题讨论】:

    标签: python recursion linked-list reverse


    【解决方案1】:

    您的 reverseGroups() 函数没有多大意义,因为它有一个 if 在两个分支中执行相同的操作。

    我将采取不同的方法。首先,我要将您的 functions 更改为 Nodemethods。接下来,我将让这些方法中的大多数递归,只是为了练习。最后,我将使链表段的反转递归,但不是重新排列链表部分的更高级别的逻辑,因为这似乎不是一个递归问题:

    class Node:
        def __init__(self, value, _next=None):
            self.value = value
            self.next = _next
    
        def printLinkedlist(self):
            print(self.value, end=' ')
    
            if self.next:
                self.next.printLinkedlist()
            else:
                print()
    
        def lengthLinkedlist(self):
            count = 1
    
            if self.next:
                count += self.next.lengthLinkedlist()
    
            return count
    
        def reverseLinkedList(self, length):
            head, rest = self, self.next
    
            if length > 1:
                if rest:
                    head, rest = rest.reverseLinkedList(length - 1)
    
                    self.next.next = self
                    self.next = None
    
            return head, rest
    
        def reverseGroups(self):
            head = self
            length = self.lengthLinkedlist()
    
            if length > 3:
                tail = self
                head, rest = self.reverseLinkedList(length//2)  # left
    
                if length % 2 == 1:  # odd, skip over middle
                    tail.next = rest
                    tail = tail.next
                    rest = tail.next
    
                tail.next, _ = rest.reverseLinkedList(length//2)  # right
    
            return head
    
    if __name__ == '__main__':
    
        head = Node(1)
        head.next = Node(2)
        head.next.next = Node(3)
        head.next.next.next = Node(4)
        head.next.next.next.next = Node(5)
        head.next.next.next.next.next = Node(6)
        head.next.next.next.next.next.next = Node(7)
    
        print('original linked list is: ', end='')
        head.printLinkedlist()
    
        head = head.reverseGroups()
    
        print('reverse of linked list is ', end='')
        head.printLinkedlist()
    

    输出

    > python3 test.py
    original linked list is: 1 2 3 4 5 6 7 
    reverse of linked list is 3 2 1 4 7 6 5 
    > 
    

    如果我们注释掉最后一个链接:

    # head.next.next.next.next.next.next = Node(7)
    

    那么我们的输出是:

    > python3 test.py
    original linked list is: 1 2 3 4 5 6 
    reverse of linked list is 3 2 1 6 5 4 
    >
    

    对我来说,这个问题原来是一个小心记账的问题。我还必须首先迭代地实现reverseLinkedList(),让reverseGroups() 工作,然后返回并递归地重新实现reverseLinkedList()

    【讨论】:

      【解决方案2】:

      我会逐步解决这个问题。对于初学者来说,可以对类结构进行一些大修以使其更易于使用。我会创建一个LinkedList 类并利用诸如__repr____len____iter__ 之类的dunder 方法来使代码更简洁、更Pythonic。根据PEP-8,使用snake_case 而不是camelCase

      from functools import reduce
      
      class Node:
          def __init__(self, value, next_=None):
              self.value = value
              self.next = next_
      
          def __repr__(self):
              return str(self.value)
      
      class LinkedList:
          def __init__(self, els):
              self.head = None
      
              for e in reversed(els):
                  self.head = Node(e, self.head)
      
          def __iter__(self):
              curr = self.head
      
              while curr:
                  yield curr
                  curr = curr.next
      
          def __getitem__(self, i):
              try:
                  return list(zip(self, range(i + 1)))[-1][0]
              except IndexError:
                  raise IndexError(i)
      
          def __len__(self): # possibly better to cache instead of compute on the fly
              return reduce(lambda a, _: a + 1, self, 0)
      
          def __repr__(self):
              return "[" + "->".join([str(x) for x in self]) + "]"
      
      if __name__ == "__main__":
          for length in range(8):
              ll = LinkedList(list(range(length)))
              print("original:", ll)
      

      在深入研究算法之前,我应该注意链表不适合递归(除非语言是 tail-call 优化的),因为每个递归步骤只会将问题空间减少 1 个节点。这会产生大量调用开销,如果列表的元素超过 1000 个,则可能会导致堆栈崩溃,并且通常不会提供太多优雅/可读性的回报来抵消这些缺点。

      另一方面,树更适合递归,因为在遍历平衡良好的树时调用堆栈会频繁弹出,从而将深度保持在对数而不是线性比例。使用快速排序或合并排序或执行二进制搜索对列表进行排序也是如此。

      链表算法还往往需要对前一个和下一个节点的许多引用,以及用于虚拟头和尾的簿记节点,这些状态在离散堆栈帧中不容易访问。通过迭代,您可以直接从循环块中访问您需要的所有状态,无需参数。

      也就是说,这是一个简单的迭代反转例程,我将用作编写其余代码的基础:

      class LinkedList:
          # ...
          def reverse(self):
              prev = None
              curr = self.head
      
              while curr:
                  nxt = curr.next
                  curr.next = prev
                  prev = curr
                  curr = nxt
          
              self.head = prev
      

      这需要更通用:如果我可以重构该算法以反转节点和子列表长度之间的列表子集,那么问题就基本解决了,因为我们可以将该算法应用于前半部分和后半部分单独的列表。

      第一步是避免硬编码self.head 并将其作为参数传递,返回反向子列表的新头。这仍然存在(我认为这是一项要求):

      class LinkedList:
          # ...
          def _reverse_from(self, curr):
              prev = None
      
              while curr:
                  nxt = curr.next
                  curr.next = prev
                  prev = curr
                  curr = nxt
          
              return prev
      
          def reverse(self):
              self.head = self.reverse_from(self.head)
      

      接下来,我们可以添加一个索引计数器来启用从节点开始的链表子集的反转。

      为了完成这项工作,新的尾节点(子列表的旧前端节点)需要链接到反向子节后左侧的列表后面,否则我们将结束旧的头/新尾节点指向到None 并砍掉尾巴。

      class LinkedList:
          # ...
          def _reverse_from(self, start, length=-1):
              curr = start
              prev = None
              
              while curr and length != 0:
                  nxt = curr.next
                  curr.next = prev
                  prev = curr
                  curr = nxt
                  length -= 1
      
              if start:
                  # link the new tail (old head) with the back of the list
                  start.next = curr
      
              return prev
      

      最后,添加面向客户端的功能,分别反转每一半:

      class LinkedList:
          # ...
          def reverse_halves(self):
              length = len(self)
          
              if length < 4:
                  return
          
              mid_idx = length // 2 
              self.head = self._reverse_from(self.head, mid_idx)
          
              if length % 2 == 0:
                  mid_idx -= 1
          
              mid = self[mid_idx]
              mid.next = self._reverse_from(mid.next)
      

      将所有这些与示例运行放在一起,我们得到:

      from functools import reduce
      
      class Node:
          def __init__(self, value, next_=None):
              self.value = value
              self.next = next_
      
          def __repr__(self):
              return str(self.value)
      
      class LinkedList:
          def __init__(self, els):
              self.head = None
      
              for e in reversed(els):
                  self.head = Node(e, self.head)
      
          def _reverse_from(self, start, length=-1):
              curr = start
              prev = None
              
              while curr and length != 0:
                  nxt = curr.next
                  curr.next = prev
                  prev = curr
                  curr = nxt
                  length -= 1
         
              if start:
                  start.next = curr
      
              return prev
      
          def reverse(self):
              self.head = self._reverse_from(self.head)
      
          def reverse_halves(self):
              length = len(self)
      
              if length < 4:
                  return
      
              mid_idx = length // 2 
              self.head = self._reverse_from(self.head, mid_idx)
      
              if length % 2 == 0:
                  mid_idx -= 1
      
              mid = self[mid_idx]
              mid.next = self._reverse_from(mid.next)
      
          def __iter__(self):
              curr = self.head
      
              while curr:
                  yield curr
                  curr = curr.next
      
          def __getitem__(self, i):
              try:
                  return list(zip(self, range(i + 1)))[-1][0]
              except IndexError:
                  raise IndexError(i)
      
          def __len__(self):
              return reduce(lambda a, _: a + 1, self, 0)
      
          def __repr__(self):
              return "[" + "->".join([str(x) for x in self]) + "]"
      
      if __name__ == "__main__":
          for length in range(8):
              ll = LinkedList(list(range(length)))
              print("original:", ll)
              ll.reverse_halves()
              print("reversed:", ll, "\n")
      

      输出:

      original: []
      reversed: []
      
      original: [0]
      reversed: [0]
      
      original: [0->1]
      reversed: [0->1]
      
      original: [0->1->2]
      reversed: [0->1->2]
      
      original: [0->1->2->3]
      reversed: [1->0->3->2]
      
      original: [0->1->2->3->4]
      reversed: [1->0->2->4->3]
      
      original: [0->1->2->3->4->5]
      reversed: [2->1->0->5->4->3]
      
      original: [0->1->2->3->4->5->6]
      reversed: [2->1->0->3->6->5->4]
      

      【讨论】:

        猜你喜欢
        • 2012-06-20
        • 2011-05-03
        • 2016-08-29
        • 1970-01-01
        • 1970-01-01
        • 2021-01-12
        • 1970-01-01
        • 2012-06-25
        相关资源
        最近更新 更多