算法通过村第二关 | k个一组反转及链表总结

1. K个一组反转

LeetCode25.给你一个链表,每K个结点一组进行反转,请你返回反转后的链表,k是一个正整数,它的值小于或等于链表长度,如果结点总数不是k的整数倍,那么请将最后剩余的结点保持原有顺序。

这个问题画图非常复杂,读者要一边想一边画,

1.1 头插法

        关键理解部分在于如何运用虚拟结点,虚拟节点理解了,两种方法并没有那么难,头插法顾名思义就是一直在头部插入,这里主要分为,已经反转,正在反转和未反转三个部分。反转之前要遍历一次链表获取长度len,确定要分为几组n = len/k,然后运用头部插入思想,

这里先讲一下一组链表运用头插法反转:http://t.csdn.cn/O2Ifr

搞明白头插法和穿针引线法再来看在这个题:

这里以讲解第二组为例,第二组以node(1)作为虚拟结点, node(4)作为第一次的头部结点,

注意:正在反转链表的交换次数,即什么时候停止,什么时候转到下一组。

下一组以上组的尾结点作为虚拟结点,再运用头插法,依次类推,看代码实现:

    public static ListNode reverseKGroup(ListNode head,int k){
        //1.先计算链表长度len
        ListNode cur = head;
        int len = 0;
        while (cur != null){
            cur = cur.next;
            len++;
        }
        //2.计算反转几组
        int n = len/k;
        //3.创建虚拟节点,头插法反转,注意反转几组,及每组反转的次数。
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        ListNode pre = dummy;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < k - 1; j++) {
                ListNode next = cur.next;
                cur.next = next.next;
                next.next = pre.next;
                pre.next = next;
            }
            //4.外层控制组数,每一组反转完之后,重新定pre和cur;
            pre = cur;
            cur = cur.next;
        }
        return dummy.next;
    }

1.2 穿针引线法

        与头插法一致,要分组反转,一组一组的处理,将其分成,已经反转,正在反转和未反转三部分,为了处理好头结点,我们新建虚拟头结点,

在遍历的过程中,根据是否为k我们要找到四个关键位置,用pre、start、end和next标志

上边文章有讲解穿针引线法:http://t.csdn.cn/V0uBn

首先需要遍历根据k值找寻end结点指向的位置,对正在反转的部分进行反转,将end.next = null,同头插法差不多,用上一组的node(1)作为虚拟节点辅助反转,我们将反转方法独立出来,head表示反转方法的参数,

 

反转完成然后进行缝补,和调整指针的指向,

 

 调整变量位置为下一次反转做准备。

 代码实现:

    public static ListNode reverseKGroup(ListNode head, int k) {
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        ListNode pre = head;
        ListNode end = head;
        //1.找到需要处理的区间,end的位置
        while (end.next != null) {
            //这里不够k长度整数倍则保持原有顺序
            for (int i = 0; i < k && end != null; i++) {
                end = end.next;
            }
            if (end == null) {
                //上次已经缝补好,这里只要跳出就好
                break;
            }
            //2.将需要处理的区间截取下来
            ListNode start = pre.next;
            ListNode next = end.next;
            end.next = null;
            //3.执行反转,
            //4.pre.next 和 start.next将反转的区间缝补回去
            pre.next = reverse(start);
            start.next = next;
            //5.调整指针为下一组反转做准备
            pre = start;
            end = pre;
        }
        return dummy.next;
    }

    public static ListNode reverse(ListNode head) {
        ListNode pre = null;
        ListNode cur = head;
        while (cur != null) {
            ListNode next = cur.next;
            cur.next = pre;
            pre = cur;
            cur = next;
        }
        return pre;
    }

2. 链表总结

        在做题的时候,脑中要有图,一道题,一张图,链表在算法专栏的前几篇文章都有讲解,接下来也会持续更新,哈哈哈,有中大v的感觉。

2.1 链表的概念、创建、遍历、插入、删除。

        算法的基础是数据结构,任何数据结构的基础就是增删改查,基础很重要,在专栏的第一篇文章就讲解了插入,这里总结下删除吧,文章连接:http://t.csdn.cn/ULcd7

 头部删除

 尾部删除

 中间删除

 2.2 判断链表是否是回文序列

LeetCode234,解题,将链表元素全部压入栈中,一边遍历链表, 一边出栈,只要有一个不相等,就不是回文序列,

也可用快慢双指针,找取中间结点,压入一半元素,然后一边出栈,一边遍历后半部分元素,

代码实现:

    public static boolean isHuiWen(ListNode head){
        ListNode cur = head;
        Stack<Integer> s = new Stack<>();
        //将链表压入栈中,
        while (cur != null){
            s.push(cur.val);
            cur = cur.next;
        }
        //一边出栈一边遍历比较
        while (head != null){
            if (head.val != s.pop()){
                return false;
            }
            head = head.next;
        }
        return true;
    }

2.3 合并有序链表

LeetCode21,将两个升序链表合并成一个新的链表并返回,新链表通过拼接给定的两个链表所有结点组成的,

新建一个链表,然后分别遍历两个链表,每次选择最小结点接入新的链表,有一个链表先被排完,将另一个链表拆下来,拼接上去,过程本身就是插入和删除操作。

    public static ListNode merageTwoList(ListNode head1,ListNode head2){
        //创建虚拟链表,指向新链表的第一个元素
        ListNode newHead = new ListNode(0);
        ListNode res = newHead;
        while (head1 != null && head2 != null){
            //情况一:都不为空
            if (head1 != null && head2 != null){
                if (head1.val < head2.val){
                    newHead.next = head1;
                    head1 = head1.next;
                }else if (head1.val > head2.val){
                    newHead.next = head2;
                    head2 = head2.next;
                }else {
                    //相等的情况,将两个都连接
                    newHead.next = head1;
                    head1 = head1.next;
                    newHead = newHead.next;
                    newHead.next = head2;
                    head2 = head2.next;
                }
                newHead = newHead.next;
            }else if(head1 != null && head2 == null){
                newHead.next = head1;
                head1 = head1.next;
                newHead = newHead.next;
            } else if(head1 == null && head2 != null){
                newHead.next = head2;
                head2 = head2.next;
                newHead = newHead.next;
            }
        }
        return res.next;
    }

2.4 双指针专题

2.4.1 寻找中间结点

LeetCode876,一个非空链表,返回中间 结点,如果有两个则返回第二个。 

        寻找中间结点就是快慢指针问题,快指针一次走两步,慢指针一次走一步,快指针到达标为的时候,慢指针正好在中间结点。

需要考虑的是偶数个结点的时候是返回哪一个,标准快慢指针返回的是中间两个的第二个。

代码实现:

    public static ListNode middleNode(ListNode head){
        ListNode slow = head , fast = head;
        while (fast != null && fast.next != null){
            slow = slow.next;
            fast = fast.next.next;
        }
        return slow;
    }

2.4.2寻找倒数第k个元素

 使用快慢指针,先将fast遍历到k+1个结点,注意不是遍历k+1次,slow仍指向第一个结点,此时两者正好相差k个结点,之后两指针同步向后走,fast指向空结点的时候,slow整好指向倒数第k个结点。

    public static ListNode getEndk(ListNode head, int k){
        ListNode slow = head , fast = head;
        //1.快指针走k次走到k+1结点位置
        while (fast != null && k >0){
            fast = fast.next;
            k--;
        }
        while (fast != null){
            fast = fast.next;
            slow = slow.next;
        }
        return slow;
    }

2.4.3旋转链表

LeetCode61,给你一个链表的头结点head,旋转链表,将链表每个结点向右移动k个位置。

 解题思路:先用快慢指针找到k的位置,然后将两个链表拼接,k有可能大于链表长度,需要先获取链表长度,k = k % len,如果k=0则链表不用反转,直接返回头结点:

    public static ListNode reverse(ListNode head, int k) {
        if (head == null && k == 0) {
            return head;
        }
        ListNode temp = head;
        ListNode slow = head;
        ListNode fast = head;
        //1.head先走一遍,统计出链表的个数,,完成之后head就变成null
        int len = 0;
        while (head != null) {
            head = head.next;
            len++;
        }
        if (k % len == 0) {
            return temp;
        }
        //2.快指针先走k步,取模是防止k>len
        while ((k % len) > 0) {
            fast = fast.next;
            k--;
        }
        //3. 快慢指针一起走找到k的前一个位置,因为用的是fast.next
        while (fast.next != null) {
            fast = fast.next;
            slow = slow.next;
        }
        //4.将k位置,断开两条链表拼接,记录slow.next作为头部
        ListNode res = slow.next;
        slow.next = null;
        fast.next = temp;
        return res;
    }

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值