- package code.code_02;
-
- /**
- * LeetCode https://leetcode.cn/problems/reverse-nodes-in-k-group/
- * K个节点的组内逆序调整
- * 给定一个单链表的头节点head,和一个正数k
- * 实现k个节点的小组内部逆序,如果最后一组不够k个就不调整
- * 调整前:1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8,k = 3
- * 调整后:3 -> 2 -> 1 -> 6 -> 5 -> 4 -> 7 -> 8
- *
- * 解题思路:
- *
- *
- */
- public class ReverseKGroupTest2 {
-
- private static class ListNode
{ - public V data;
- public ListNode next;
-
- ListNode (V _data){
- this.data = _data;
- }
- }
-
- // 先设计打印方法,方便检查逆转后结果
- public void printNode (ListNode node)
- {
- if (node == null) {
- System.out.println("链表不存在");
- }
- System.out.println("当前节点的值为: " + node.data);
- //递归的方式逐层打印Node的子节点
- if(node.next != null) {
- printNode(node.next);
- }
- }
-
- //设计构造单链表的函数
- public ListNode initSingleNodeList (int length)
- {
- if (length <= 0) {
- return null;
- }
- ListNode head = null;
- ListNode
cur; - ListNode
pre = null; - for (int i = 1; i <= length; i++) {
- cur = new ListNode<>(i);
- //头结点一旦确认,将会固定不变
- if (head == null) {
- head = cur;
- }
- if (pre != null) {
- pre.next = cur;
- }
- pre = cur;
- }
- return head;
- }
-
- public ListNode getEndNodeInKGroup (ListNode node, int k)
- {
- /* while (k > 0 && node != null) {
- node = node.next;
- k--;
- }*/
-
- //以上注释的代码是有bug的写法,边边角角需要注意
- while (--k > 0 && node != null) {
- node = node.next;
- }
- return node;
- }
-
- public ListNode reverseKGroup (ListNode node, int k)
- {
- ListNode start = node;
- ListNode end = this.getEndNodeInKGroup(start, k);
-
- //第一次进入都没有尾节点,说明此链表不符合局部逆转
- //k个节点的要求,直接返回
- if (end == null) {
- return node;
- }
- //逆转前的第一个分组尾节点,就是新的头结点
- ListNode head = end;
-
- /**
- * 第一次逆转。为什么没有返回值呢?
- * 因为head已经指向了新的头结点, 此方法做到逆转,
- * 使链表并没有断.
- * 此时的head是第一次局部逆转后的值。比如:
- * 逆转前是 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7
- * 逆转后是 3 -> 2 -> 1 -> 4 -> 5 -> 6 -> 7
- */
- System.out.println("========外部调用前hash值为: " + end.hashCode() + "======");
- reverseNode(start, end);
- System.out.println("========外部调用后hash值为: " + end.hashCode() + "======");
-
- System.out.println("+++++++++++++++++++++++++++一次完整的调用结束++++++++++++++++++++++++++++++++");
-
- //上一组group你转完以后的尾节点
- ListNode lastEnd = start;
- while (lastEnd.next != null) {
- /**
- * 接着逆转
- * 因为reverseNode(start, end)并没有返回值,因此原有的连
- * 表并没有断。由于在 reverseNode(start, end)方法中的逆
- * 转。 此时的start已经持有了下一组待逆转group的头节点处
- * 可以参考reverseNode(start, end)方法的最后一行代码
- */
- start = lastEnd.next;
- end = this.getEndNodeInKGroup(start, k);
- //如果接下来的group不符合算法要求,也就是不满足
- //k个节点,直接返回
- if (end == null) {
- //因为没有发生逆转,前一组已经逆转的尾结点本来就
- //持有这一组待你转接节点的默认头节点,也就不需要更新了
- return head;
- }
- System.out.println("========外部调用前hash值为: " + end.hashCode() + "======");
- reverseNode(start, end);
- System.out.println("========外部调用后hash值为: " + end.hashCode() + "======");
- System.out.println("+++++++++++++++++++++++++++一次完整的调用结束++++++++++++++++++++++++++++++++");
-
- //上一组的尾结点,需要持有本组你转完以后的头结点
- lastEnd.next = end;
- //lastEnd来到分组你转完以后的尾节点,为下轮循环做准备
- lastEnd = start;
- }
-
- //直接返回的是head, 也解释了为什么reverseNode(start, end)
- //可以没有返回值了.
- //如果有兴趣,可以去改写的我的算法2中逆转整个链表的代码。将有返回值的逆转
- //方法也改成没有返回值的方法https://blog.csdn.net/chen_yao_kerr/article/details/127935045?spm=1001.2014.3001.5501
- return head;
- }
-
- //无参数函数,因此之前的连边不能断
- private void reverseNode (ListNode start, ListNode end)
- {
- //我们需要提前直到逆转前的end节点的下一个节点
- //逆转完成以后,新的尾节点指向此节点,保证链表不断
-
- //此处有一个问题,此处的end指向了下一个节点,那么end应该是变化了的才对。那么为什么
- //在代码的122行lastEnd.next = end; 依旧持有值为6的节点,而不是持有值为7的节点呢?
-
- //其实,此处的end经过一次赋值,相当于在另一个方法栈中生成了一个新的局部变量,只是
- //他们的名称相同而已。其实他们根本就不是一个东西 >> 此处我们通过hash值来确认
- System.out.println("========方法体内部调用前的hash值为: " + end.hashCode() + "======");
- end = end.next;
- System.out.println("========方法体内部调用后的hash值为: " + end.hashCode() + "======");
-
- ListNode next = null;
- ListNode cur = start; //start节点是新的尾节点,后面还有用。此时定义一个新的临时节点
- ListNode pre = null; //已经逆转的节点
- //开始逆转
- //此时的end节点是下一组待逆转的头结点
- while (cur != end) {
- next = cur.next;
- cur.next = pre;
- pre = cur;
- cur = next;
- }
-
- /**
- * 此处有2个问题需要解释一下
- * 问题1: 为什么是start.next 而不是cur.next呢 ?
- * 原因: 因为cur已经来到了第一个待逆转group的尾结点,也就是
- * 你转完以后group的头结点处
- *
- * 问题2:为什么start.next = pre 而不是start.next = cur ?
- * 原因: 因为我们在方法的开始就设置了end = end.next; 这样可以
- * 保证我们当前group的节点都可以在87行的while方法中遍历到。
- * 而cur最后一次是来到了end.next的位置,这已经超出了当前group的
- * 节点范畴,因此需要往前找一个,也就是上一次逆转的节点
- */
- start.next = end;
- }
-
- public static void main(String[] args) {
- ReverseKGroupTest2 test = new ReverseKGroupTest2();
-
- //此参数可以设计成从UI传递的值
- int length = 8;
- //初始化固定长度的链表
- ListNode node = test.initSingleNodeList(length);
- System.out.println("===============测试构造的链表是否正常===================");
- test.printNode(node);
-
- //链表进行按照k个数组进行划分,并进行局部逆转的过程
- ListNode n = test.reverseKGroup(node, 3);
- System.out.println("===============打印局部逆转后的结果===================");
- test.printNode(n);
-
-
- }
- }
结果如下:
===============测试构造的链表是否正常===================
当前节点的值为: 1
当前节点的值为: 2
当前节点的值为: 3
当前节点的值为: 4
当前节点的值为: 5
当前节点的值为: 6
当前节点的值为: 7
当前节点的值为: 8
========外部调用前hash值为: 1163157884======
========方法体内部调用前的hash值为: 1163157884======
========方法体内部调用后的hash值为: 1956725890======
========外部调用后hash值为: 1163157884======
+++++++++++++++++++++++++++一次完整的调用结束++++++++++++++++++++++++++++++++
========外部调用前hash值为: 356573597======
========方法体内部调用前的hash值为: 356573597======
========方法体内部调用后的hash值为: 1735600054======
========外部调用后hash值为: 356573597======
+++++++++++++++++++++++++++一次完整的调用结束++++++++++++++++++++++++++++++++
===============打印局部逆转后的结果===================
当前节点的值为: 3
当前节点的值为: 2
当前节点的值为: 1
当前节点的值为: 6
当前节点的值为: 5
当前节点的值为: 4
当前节点的值为: 7
当前节点的值为: 8Process finished with exit code 0
实测结果:

其实,逐步拆分算法,组成每一个小例子,然后将小例子都理解了,再组合在一起就是一个完整的算法了。先理解这个算法想要干什么,对照自己的例子去实现就可以了。代码里面备注信息很全面,相信对于首次接触这个算法的你会有帮助