61-RotateList

旋转列表

给定一个链表,旋转链表,将链表每个节点向右移动 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

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/rotate-list
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

和旋转字符串的思路类似,都是循环移位的思想,像取模,空间换时间,时间换空间,三次翻转法等。链表的操作没有字符串那样方便操作,所以三次翻转这种的就很麻烦了。下面是 python 完成的(链表在 C 语言中使用结构体完成,在 python 中用类完成,思路是一样的)。首先是确定链表长度(为了确定链表断开和新链表开始的位置);然后能定位到任意节点,对这个节点进行新建链表或者拆除链表的工作。
我感觉自己代码的问题就在这里,也就是节点定位,这导致代码的性能一般。

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

def LenListNode(ListNode):
    count = 0
    while ListNode is not None:
        count += 1
        ListNode = ListNode.next
    else:
        return count

def ReachListNode(ListNode, i):
    count = 0
    while ListNode:
        if i == count:
            return ListNode
            break
        else:
            count += 1
            ListNode = ListNode.next

class Solution:
    def rotateRight(self, head: ListNode, k: int) -> ListNode:
        n = LenListNode(head)
        if n == 0:
            return None
        else:
            k = k % n
        if k == 0:
            return head
        new_end = ReachListNode(head, n-k-1)
        ReachListNode(new_end, k).next = head
        head = ReachListNode(new_end, 1)
        new_end.next = None
        return head

代码改了较长时间。这个过程中自己一共犯了两个错误:

  • 自己的前两次提交,都是忽略了 n 的判断,导致提交失败。n 需要先判断是否为 0 才能进行除法,取余等操作。这个在以后的提交中需要注意。
  • 最后对于链表进行整合时,正确顺序是:尾节点指向头节点 -- 确定新的头节点 -- 确定尾节点。自己先确定了尾节点,这样就把整个链破坏了,再想确定头节点就不容易了。

其他思路

直接思路

先把单向链表变成循环链表;找到断开位置;重新确定头尾节点即可。这也是自己的思路,这种思路最大瓶颈就是定位节点:已知头节点,需要定位 尾节点 和 新的头/尾节点。
下面是较为完整的代码实现:实际上下面的代码执行时间是64ms,比自己的多出20ms;内存相似。自己和它最大的区别就是使用了函数封装了功能;在遍历次数上都是两遍。我觉得这个差别不大。而且如果真的要从效率而言,C/C++才是最佳选择。
这段代码的另一个优点是较好的书写习惯和较为完整的考虑了各种情况:

class Solution:
    def rotateRight(self, head: 'ListNode', k: 'int') -> 'ListNode':
        # base cases
        if not head:         # 空链表
            return None
        if not head.next:    # 链表只有一个元素
            return head
        
        # close the linked list into the ring
        old_tail = head
        n = 1
        while old_tail.next:
            old_tail = old_tail.next    # 定位到尾节点
            n += 1                      # 确定链表长度
        old_tail.next = head
        
        # find new tail : (n - k % n - 1)th node
        # and new head : (n - k % n)th node
        new_tail = head
        for i in range(n - k % n - 1):
            new_tail = new_tail.next    # 新链表的尾节点(new_end)
        new_head = new_tail.next        # 新链表的头节点
        
        # break the ring
        new_tail.next = None
        
        return new_head

作者:LeetCode
链接:https://leetcode-cn.com/problems/rotate-list/solution/xuan-zhuan-lian-biao-by-leetcode/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

双指针

这是针对上面思路的优化,对于效率的提升很明显。做法是:

  • 指针 P1 先从 0 遍历到 K,指针 P2 开始从0遍历
  • 两者之间始终差 K,直到 P1 遍历到尾节点
  • P1 指向旧链表的尾节点;P2指向新链表的尾节点,P2的下一个节点就是新链表的头节点
    如果 K 比较小,可以不用遍历得到链表长度;但是,如果 K 很大,那么就必须要先遍历,不然就会超时。

用列表模拟链表

既然链表不好操作,那么先把所有元素存到列表中;然后用一行代码(189-RotateArray中python只用一行代码实现)实现;最后再转换成链表。

总结

链表题目做到最后,指针(单指针/双指针) + 列表(模拟队列/栈) 的策略用得较多。双指针的策略效率更高。直接去解也可以,只要代码写的六,调试调的快,就没有问题。

猜你喜欢

转载自www.cnblogs.com/rongyupan/p/12633050.html
61
61-
61A
T61