题目描述
给定一链表,旋转链表,将链表每个节点向右移动k
个位置,其中k
是非负数。
示例1:
输入: head = [1, 2, 3, 4, 5], k=2
输出: head = [4, 5, 1, 2, 3]
示例2:
输入: head = [0, 1, 2], k=4
输出: head = [2, 0, 1]
思路分析
直观地理解这道题,即将tail
节点一次次的设置为head
,设置的次数为k
; 这里我们不难发现,会存在链表长度l
与k
的比较关系:
l < k
时,需要转移的是链表的一部分;l = k
时,则为原链表;l > k
时,则为几次无用功 +l < k
,这时我们需要对l
对k
取余,即l % k
。
在上述描述过程中,我们其实涉及到两个问题:
- 如何以较小代价取得链表长度信息
l
?或者是如何在获取长度信息的同时做一些有用功? - 当我们得到取余后的
k
时如何实现需要重新链接的这部分节点?
这里为方便讲解,我们做出图示:
这里我们先考虑两个节点间的互换,可以看图1:
图 1
如图一所示,我们对原始链表(a)移动了一次节点,得到(b),移动两次得到©,我们不免下意识地会想到 循环链表。因为这里只涉及到了链表head
与tail
的节点变化,我们将图1,以另一种方式来展示会更直观:
图2
在图2中,我们在原始链表的尾节点5
添加了一条辅助指针,且用绿色虚线标注出来。通过图2,我们可以清晰的看到,在链表节点移动的过程中,可以进一步看作是head
与tail
指针指向的节点变化,这样的角度进一步简化了解题的思路。
步骤罗列
- 通过遍历找到尾节点
oldTail
,并使其与当前头节点链接oldTail.next = head
,此时会构造出循环链表; - 找到新的尾节点
newTail
, 且此时新的头节点为其下一节点head = newTail.next
; - 更新
newTail.next = null
,返回新的链表头。
解题代码
public static ListNode solution(ListNode head, int k) {
if (head == null || k == 0 || head.next == null) {
return head;
}
//init pointers
ListNode oldTail = head;
ListNode newTail = head;
ListNode newHead;
int len; //record the length of list
/* Step 1:
oldTail move forward to get length of list
and
make list to be a circular one
*/
for(len = 1; oldTail.next != null; len++){
oldTail = oldTail.next;
}
oldTail.next = head;
/* Step 2:
upgrate newTail and newHead
*/
for(int i = 0; i < (len - k%len -1);i++)
newTail = newTail.next;
newHead = newTail.next;
/* Step 3:
upgrate newTail.next and return result
*/
newTail.next = null;
return newHead;
}
复杂度分析
时间复杂度:我们对数据进行了不止一次遍历,但小于两次遍历,故依然为线性级复杂程度,所以时间复杂度为O(N);(这里N与思路分析中的l
为同一变量)
空间复杂度:我们没有借助额外的容器,所以空间复杂度为常量级O(1)。
GitHub源码
完整可运行文件请访问GitHub。