赞
踩
class Solution { public ListNode reverseKGroup(ListNode head, int k) { // 创建保护节点 ListNode protect = new ListNode(-1, head); ListNode last = protect; while(head != null) { // 获取节点尾部 // 如果不满足条件, 比如 仅剩2个节点, 但是要返回3组, 就会直接返回null ListNode tail = getTail(head, k); // 边界问题 : tail = null , 最后一组不满足 k 个 if (tail == null) { break; } // 记录尾部节点的下一个节点 ListNode nextNode = tail.next; // 反转 head 到 tail 之间 n - 1 条边 reverse(head, tail); // 上一组的尾部(未反转前的头部)指向当前组的头部(未反转前的尾部) last.next = tail; head.next = nextNode; // 向后移动 last = head; head = nextNode; } return protect.next; } public ListNode getTail(ListNode head, int k){ int walk = k - 1; // 实际要走的步数 if(walk <= 0) { return head; } for(int i = 0; head != null && i < walk; i++ ) { head = head.next; } return head; } private void reverse(ListNode head, ListNode tail){ if (head == tail) { return; } // 这里注意, 我们不需要改变第一个节点的边的指向 // 所以原先 last = null => last = head , 向后移动一位 ListNode last = head; head = head.next; while(head != tail) { ListNode nextNode = head.next; // 改变边的方向 head.next = last; // 指针向后移动 last = head; head = nextNode; } // 注意 head 移动到 tail 位置时, 最后一条边的方向还未改变 tail.next = last; } }
基本思路如下 :
主体思路很简单, 分组去反转对应部分的链表, 然后再去考虑和当前组前面和后面节点的链接
首先我们要实现一个获取链表尾部的方法 :
head
和 链表的个数n
返回链表的尾部 public ListNode getTail(ListNode head, int k){
int walk = k - 1; // 实际要走的步数
if(walk <= 0) {
return head;
}
for(int i = 0; head != null && i < walk; i++ ) {
head = head.next;
}
return head;
}
代码很简单, 但是要注意一些细节 :
head != null
这个条件, 当 最后一组的链表节点个数小于 k - 1的时候, 这个时候 还未循环结束, head 可能就走到了链表的末尾, 注意 : 此时 我们要返回的 tail 并不是链表的末尾, 而是 null其次我们要将 链表的头部和尾部节点传入到 reverse
方法内去反转链表的边, 这个思路和 206.反转链表 的思路基本一致, 但是有一点不同
private void reverse(ListNode head, ListNode tail){ if (head == tail) { return; } // 这里注意, 我们不需要改变第一个节点的边的指向 // 所以原先 last = null => last = head , 向后移动一位 ListNode last = head; head = head.next; while(head != tail) { ListNode nextNode = head.next; // 改变边的方向 head.next = last; // 指针向后移动 last = head; head = nextNode; } // 注意 head 移动到 tail 位置时, 最后一条边的方向还未改变 tail.next = last; }
注意细节 :
反转链表时, 初始状态 last 是指向 null 的, 假如有5个节点, 我们实际上要反转 5条边, 包含开头指向null的边
但是在这道题里面, 我们仅仅需要反转 n - 1 条边,如下图, 实际上我们要反转的是 节点 1 到 节点 2 的边即可
细节问题: 如果head和tial指向同一个节点, 我们就不需要反转边
if (head == tail) {
return;
}
然后, 我们需要把反转后的链表和其他节点连接, 另外需要注意的是, 我们考虑问题先考虑整体, 再考虑细节, 所以我们考虑中间的节点, 因为中间的节点不存在边界问题 :
public ListNode reverseKGroup(ListNode head, int k) { // 创建保护节点 ListNode protect = new ListNode(-1, head); ListNode last = protect; while(head != null) { // 获取节点尾部 // 如果不满足条件, 比如 仅剩2个节点, 但是要返回3组, 就会直接返回null ListNode tail = getTail(head, k); // 边界问题 : tail = null , 最后一组不满足 k 个 if (tail == null) { break; } // 记录尾部节点的下一个节点 ListNode nextNode = tail.next; // 反转 head 到 tail 之间 n - 1 条边 reverse(head, tail); // 上一组的尾部(未反转前的头部)指向当前组的头部(未反转前的尾部) last.next = tail; head.next = nextNode; // 向后移动 last = head; head = nextNode; } return protect.next; }
首先在反转链表之前需要记录尾部节点4的下一个节点5 : ListNode nextNode = tail.next;
因为反转以后, 节点 4 和 5之间的连接就断了
其次就是对链表进行反转, 然后将当前组和上一组的尾部与下一组的头部连接 :
last.next = tail;
head.next = nextNode;
最后我们需要重新修改last节点指向当前组的尾部节点, 修改head节点指向下一组的头部节点
// 向后移动
last = head;
head = nextNode;
另外注意细节问题: 最后一组不满足k个的时候, 我们不需要反转, 所以当我们获取尾部以后 需要判断下
// 边界问题 : tail = null , 最后一组不满足 k 个
if (tail == null) {
break;
}
其次是保护节点 : 之所以要有保护节点的原因是, 我们考虑问题是考虑中间组的节点, 但是对于头结点来说, 它没有上一组的节点, 且无法获取 last (也就是上一组的尾结点) , 但是保护节点就可以很好的解决问题 :
初始时 last 直接指向 protect 节点即可, 头结点也不需要考虑边界问题.
完整代码如下 :
package cn.knightzz.leetcode.hard; import cn.knightzz.link.ListNode; import static cn.knightzz.link.utils.ListNodeUtils.*; @SuppressWarnings("all") public class LeetCode25 { class Solution { public ListNode reverseKGroup(ListNode head, int k) { // 创建保护节点 ListNode protect = new ListNode(-1, head); ListNode last = protect; while(head != null) { // 获取节点尾部 // 如果不满足条件, 比如 仅剩2个节点, 但是要返回3组, 就会直接返回null ListNode tail = getTail(head, k); // 边界问题 : tail = null , 最后一组不满足 k 个 if (tail == null) { break; } // 记录尾部节点的下一个节点 ListNode nextNode = tail.next; // 反转 head 到 tail 之间 n - 1 条边 reverse(head, tail); // 上一组的尾部(未反转前的头部)指向当前组的头部(未反转前的尾部) last.next = tail; head.next = nextNode; // 向后移动 last = head; head = nextNode; } return protect.next; } public ListNode getTail(ListNode head, int k){ int walk = k - 1; // 实际要走的步数 if(walk <= 0) { return head; } for(int i = 0; head != null && i < walk; i++ ) { head = head.next; } return head; } private void reverse(ListNode head, ListNode tail){ if (head == tail) { return; } // 这里注意, 我们不需要改变第一个节点的边的指向 // 所以原先 last = null => last = head , 向后移动一位 ListNode last = head; head = head.next; while(head != tail) { ListNode nextNode = head.next; // 改变边的方向 head.next = last; // 指针向后移动 last = head; head = nextNode; } // 注意 head 移动到 tail 位置时, 最后一条边的方向还未改变 tail.next = last; } } }
这个方法的思路很简单 : 先计算链表长度, 然后和 k 求余得到总共要反转多少组, 然后分组翻转即可
时间复杂度 O n O_{n} On
reverseN(ListNode head, int n)
reverseBetwwen(ListNode head, int n , int m)
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode() {} * ListNode(int val) { this.val = val; } * ListNode(int val, ListNode next) { this.val = val; this.next = next; } * } */ class Solution { public ListNode reverseKGroup(ListNode head, int k) { int count = 1; ListNode p = head; while(p.next != null) { p = p.next; count++; } int nums = count / k; int left = 1; int right = k; ListNode node = head; for (int i = 0; i < nums; i++) { node = reverseBetween(node, left, right); left += k; right+= k; } return node; } public ListNode reverseBetween(ListNode node, int left, int right) { if (node == null || node.next == null || left >= right) { return node; } int count = left; // 创建虚拟头节点 [关键] ListNode pre = new ListNode(-1, node); ListNode p = pre; while(count != 1) { p = p.next; count--; } ListNode head = reverseN(p.next,right - left + 1); p.next = head; return pre.next; } public ListNode reverseN(ListNode head, int n){ // 边界条件 if (n == 1) { return head; } // 翻后转链表的头部 ListNode tail = reverseN(head.next, n - 1); // 翻转链表的头部 , 注意 tail.next = null / 第 n + 1 个节点 ListNode p = head.next; // 原因是 如果顺序反了, tail指向的目标会丢失 head.next = p.next; p.next = head; return tail; } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。