旋转列表
给定一个链表,旋转链表,将链表每个节点向右移动 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 的判断,导致提交失败。
以后所有涉及到除法的语句,都要先判断除数是否为 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的下一个节点就是新链表的头节点
用列表模拟链表
既然链表不好操作,那么先把所有元素存到列表中;然后用一行代码(189-RotateArray中python只用一行代码实现)实现;最后再转换成链表。
总结
链表题目做到最后,指针(单指针/双指针) + 列表(模拟队列/栈) 的策略用得较多。双指针的策略效率更高。直接去解也可以,只要代码写的好,调试调的快,就没有问题。