首页 > 编程语言 >算法4: LeetCode_K个节点的组内逆序调整

算法4: LeetCode_K个节点的组内逆序调整

时间:2022-11-23 17:00:15浏览次数:43  
标签:Node end 组内 LeetCode start next 逆转 节点 逆序

  • 最近一直都是链表的算法练习,今天刷的是LeetCode原题,还是关于链表的节点逆转,难度等级:Hard.
  • 首先看题目:给定一个单聊表的头节点head和一个正整数k, 要求实现k个节点的小组内部逆序,如果最后一组不够k个就不调整。 如果给定的链表为1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8,k = 3,调整完以后链表变成 3 -> 2 -> 1 -> 6 -> 5 -> 4 -> 7 -> 8
  • 解题思路:
  1. 既然是给定长度的组内局部逆转,首先肯定是要对链表进行分组,符合条件的小组内部进行链表的逆转。不符合条件的小组,则保持不变
  2. 如果符合条件,每个小组的内部其实都设计到头节点和尾结点的对调,并且前一个小组的尾结点需要持有下一小组你转完以后的新的头节点。此处可能涉及到多次逆转。比如第一次逆转完以后链表应当变成 3 -> 2 -> 1 -> 4 -> 5 -> 6 -> 7 -> 8. 第二次逆转完变成 3 -> 2 -> 1 -> 6 -> 5 -> 4 -> 7 -> 8。第三次逆转,发现只有7和8两个节点,不符合逆转条件,则不逆转。
  3. 通过思路2我们可知,第一小组你转完以后,头节点即锁定为3,后面不会发生改变。而值为1的节点首先是持有下一个小组的头结点,即值为4的节点。但是,当下一小组节点发生逆转以后,会更改持有的头节点,会变更为持有值为6的新头节点。最后一次只有2个节点,不满足条件,不需要逆转,直接返回即可。

 

  • 总结:通过以上分享,我们大体得出结论:第一次逆转锁定整个链表的头节点 每一次逆转后,前一小组的尾结点都需要变更持有下一小组的逆转后的头结点引用对象;如果不满足条件,则直接返回,不做任何变更

package code.code_02;

/**
 * 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 Node<V> {
        public V data;
        public Node next;

        Node (V _data){
            this.data = _data;
        }
    }

    // 先设计打印方法,方便检查逆转后结果
    public void printNode (Node node)
    {
        if (node == null) {
            System.out.println("链表不存在");
        }
        System.out.println("当前节点的值为: " + node.data);
        //递归的方式逐层打印Node的子节点
        if(node.next != null) {
            printNode(node.next);
        }
    }

    //设计构造单链表的函数
    public Node initSingleNodeList (int length)
    {
        if (length <= 0) {
            return null;
        }
        Node head = null;
        Node<Integer> cur;
        Node<Integer> pre = null;
        for (int i = 1; i <= length; i++) {
            cur = new Node<>(i);
            //头结点一旦确认,将会固定不变
            if (head == null) {
                head = cur;
            }
            if (pre != null) {
                pre.next = cur;
            }
            pre = cur;
        }
        return head;
    }

    public Node getEndNodeInKGroup (Node node, int k)
    {
       /* while (k > 0 && node != null) {
            node = node.next;
            k--;
        }*/

       //以上注释的代码是有bug的写法,边边角角需要注意
        while (--k > 0 && node != null) {
            node = node.next;
        }
        return node;
    }

    public Node reverseKGroup (Node node, int k)
    {
        Node start = node;
        Node end = this.getEndNodeInKGroup(start, k);

        //第一次进入都没有尾节点,说明此链表不符合局部逆转
        //k个节点的要求,直接返回
        if (end == null) {
            return node;
        }
        //逆转前的第一个分组尾节点,就是新的头结点
        Node 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你转完以后的尾节点
        Node 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 (Node start, Node 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() + "======");

        Node next = null;
        Node cur = start; //start节点是新的尾节点,后面还有用。此时定义一个新的临时节点
        Node 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;
        //初始化固定长度的链表
        Node node = test.initSingleNodeList(length);
        System.out.println("===============测试构造的链表是否正常===================");
        test.printNode(node);

        //链表进行按照k个数组进行划分,并进行局部逆转的过程
        Node 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
当前节点的值为: 8

Process finished with exit code 0

 

其实,逐步拆分算法,组成每一个小例子,然后将小例子都理解了,再组合在一起就是一个完整的算法了。先理解这个算法想要干什么,对照自己的例子去实现就可以了

 

标签:Node,end,组内,LeetCode,start,next,逆转,节点,逆序
From: https://www.cnblogs.com/chen1-kerr/p/16918802.html

相关文章

  • Leetcode 1360. 日期之间隔几天
    题目:请你编写一个程序来计算两个日期之间隔了多少天。日期以字符串形式给出,格式为YYYY-MM-DD,如示例所示。难度:简单题示例1:输入:date1="2019-06-29",date2="2019-......
  • LeetCode[435] 无重叠区间
    https://leetcode.cn/problems/non-overlapping-intervals/description/线性dpTLEclassSolution{public:intf[200010];inta[200010];interaseOver......
  • [LeetCode] 2133. Check if Every Row and Column Contains All Numbers
    An nxn matrixis valid ifeveryrowandeverycolumncontains all theintegersfrom 1 to n (inclusive).Givenan nxn integermatrix matrix,re......
  • leetcode 88. 合并两个有序数组 js实现
    给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。请你 合并 nums2 到 nums1 中,使合......
  • LeetCode[124] 二叉树中的最大路径和
    https://leetcode.cn/problems/binary-tree-maximum-path-sum/description/dp,树上搜索因为值有负数,所以针对一个节点的更新,有四种情况:节点值本身节点值+左子树节......
  • [Leetcode Weekly Contest]320
    链接:LeetCode[Leetcode]2475.数组中不等三元组的数目给你一个下标从0开始的正整数数组nums。请你找出并统计满足下述条件的三元组(i,j,k)的数目:0<=i<j<......
  • leetcode35. 搜索插入位置(简单)
    题目描述:给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。请必须使用时间复杂度为O(logn)......
  • #yyds干货盘点# LeetCode 腾讯精选练习 50 题:删除链表中的节点
    题目:有一个单链表的 head,我们想删除它其中的一个节点 node。给你一个需要删除的节点 node 。你将 无法访问 第一个节点 head。链表的所有值都是唯一的,并且保证给定......
  • leetcode724. 寻找数组的中心下标(数组)
    题目描述:给你一个整数数组nums,请计算数组的中心下标。数组中心下标是数组的一个下标,其左侧所有元素相加的和等于右侧所有元素相加的和。如果中心下标位于数组最......
  • leetcode563. 二叉树的坡度。
    563.二叉树的坡度 二叉树大部分题目都可以用递归解决。为了满足一般性,即使题目初试没有的情况,子问题有的,也要考虑。递归就考虑当前的情况就行了,不要再考虑上一层或......