一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第15天,点击查看活动详情。
前言
笔者除了大学时期选修过《算法设计与分析》和《数据结构》还是浑浑噩噩度过的(当时觉得和编程没多大关系),其他时间对算法接触也比较少,但是随着开发时间变长对一些底层代码/处理机制有所接触越发觉得算法的重要性,所以决定开始系统的学习(主要是刷力扣上的题目)和整理,也希望还没开始学习的人尽早开始。
系列文章收录《算法》专栏中。
问题描述
给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。
k 是一个正整数,它的值小于或等于链表的长度。
如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。
进阶:
- 你可以设计一个只使用常数额外空间的算法来解决此问题吗?
- 你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。
示例 1:
输入:head = [1,2,3,4,5], k = 2
输出:[2,1,4,3,5]
示例 2:
输入:head = [1,2,3,4,5], k = 3
输出:[3,2,1,4,5]
示例 3:
输入:head = [1,2,3,4,5], k = 1
输出:[1,2,3,4,5]
示例 4:
输入:head = [1], k = 1
输出:[1]
提示:
- 列表中节点的数量在范围 sz 内
- 1 <= sz <= 5000
- 0 <= Node.val <= 1000
- 1 <= k <= sz
剖析
K个一组翻转一次,每K个进行内部调整,需要先保留内部尾部节点的next节点,方便调整后重新接上。需要新建一个头部节点方便第一组内部调整原来的头部节点不用维持,结果返回新建头部节点的next节点就绪。下面描述下具体步骤:
- 新建hair节点,原来的head节点作为hair节点的next节点。
- 开始每k个节点进行内部调整,不足k个了就直接返回hair.next。在遍历时拿到小组的头节点和尾节点,以便进行内部翻转。
- 翻转的时候头节点指向尾节点的next节点,头节点的next节点指向头节点,过程中需要把next节点进行保留,方便依次翻转。
具体还是需要看代码比较好理解。
代码
package com.study.algorithm.linklist;
import java.util.ArrayList;
import java.util.List;
public class ReverseKGroup {
public static class ListNode {
int val;
ListNode next;
ListNode() {
}
ListNode(int val) {
this.val = val;
}
ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
}
public static ListNode myReverseKGroup(ListNode head, int k) {
List<ListNode> nodeList = new ArrayList<>();
ListNode next = head;
while (next != null) {
nodeList.add(next);
next = next.next;
}
next = head;
int i = 1;
while (next != null) {
if (i % k == 0 && i < nodeList.size()) {
int j = i - 1;
if (j == (i - 1)) {
if (j - k < 0) {
head = nodeList.get(j);
} else {
nodeList.get(i - 2 * k).next = nodeList.get(j);
}
}
int n = j;
while ((n - 1) >= 0 && (n - 1) >= (i - k)) {
nodeList.get(n).next = nodeList.get(n - 1);
n--;
}
if (i <= (nodeList.size() - 1)) {
nodeList.get(i - k).next = nodeList.get(i);
} else {
nodeList.get(i - k).next = null;
}
}
next = next.next;
i++;
}
return head;
}
public static ListNode reverseKGroup(ListNode head, int k) {
//新建hair节点,方便翻转后找到头部节点
ListNode hair = new ListNode(0);
//初始的时候head作为hair的next节点
hair.next = head;
//pre节点方便遍历拿到k个节点的小组
ListNode pre = hair;
while (pre != null) {
ListNode tail = pre;
for (int i = 0; i < k; i++) {
//拿到小组的尾节点,方便后面翻转
tail = tail.next;
//一旦等于null说明已经不够k个了,可以直接返回了
if (tail == null) {
return hair.next;
}
}
//保存tail节点的next节点,方便小组内部翻转后接上
ListNode next = tail.next;
//进行内部翻转
inGroupReverse(head, tail);
//因为内部进行翻转了,所以现在的head、和tail逻辑位置已经调换
ListNode headTemp = head;
head = tail;
tail = headTemp;
//拼接上
pre.next = head;
tail.next = next;
//pre、head进行更新
pre = tail;
head = tail.next;
}
return hair.next;
}
/**
* 组内翻转
*
* @param head
* @param tail
*/
private static void inGroupReverse(ListNode head, ListNode tail) {
//当前需要处理的节点,从头部开始
ListNode currentNeedHandleNode = head;
//前面一个节点,用于变成待处理节点的next节点,需要从tail的next开始,后面为上一个处理后的节点
ListNode pre = tail.next;
while (pre != tail) {
//保留当前需要处理的next节点
ListNode next = currentNeedHandleNode.next;
//当前需要处理的节点的next节点变成pre,进行翻转
currentNeedHandleNode.next = pre;
//处理好currentNeedHandleNode变成pre节点,便于后面赋值给下一个节点
pre = currentNeedHandleNode;
//把next赋值给currentNeedHandleNode进行下次翻转
currentNeedHandleNode = next;
}
}
public static void main(String[] args) {
ListNode head = new ListNode(1);
head.next = new ListNode(2);
head.next.next = new ListNode(3);
head.next.next.next = new ListNode(4);
head.next.next.next.next = new ListNode(5);
// head.next.next.next.next.next = new ListNode(6);
ListNode listNode = reverseKGroup(head, 2);
System.out.println(listNode);
}
}