链表反转

链表反转


链表翻转是面试中经常考察的一类题型,我每次写都要不断调试,设置的辅助结点也都全靠感觉,所以在此总结。目的仅为个人面试和学习,不做研究。

我们先来介绍最简单的--反转链表

定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。

这里引用力扣题解里的图片加深理解,有头查和尾插,这里统一使用尾插。

 

需要建立两个辅助结点,cur当前节点需要赋予初始null值,pre前置结点也就是未翻转时cur结点的next结点,再迭代的过程中还需一个临时结点存储pre的next节点。上代码。

 public class Solution {
     public ListNode reverseList(ListNode head) {
         ListNode cur = null;
         ListNode pre = head;
         while (pre != null) {
             ListNode node = pre.next;
             pre.next = cur;
             cur = pre;
             pre = node;
        }
         return cur;
    }
 }

这只是一道简单的题目,确实后面题目的基石。我思考一番发现以前我做的时候没有考虑使用头插还是尾插所以造成混乱,在此引以为戒。

下面来进阶一下--反转链表 II

给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表

本题可以理解为在链表中选取一段进行反转,首先我们要获得需要反转的一段链表,然后反转再接入原链表。

由此我们需要保存原链表的的头和尾,然后还需知道需要反转的部分,这里通过遍历获得需要反转的头和尾作为新函数的入参,返回反转后的头尾进行拼接

 public class Solution {
     public ListNode reverseBetween(ListNode head, int left, int right) {
         ListNode hair = new ListNode(-1);  //虚拟头节点,这里hair的意思也很明显
         hair.next = head;
         
         //遍历获得反转部分,i的范围要注意仔细推算
         ListNode pre = hair;
         for (int i = 0; i < left - 1; i++) {
             //这里要获得需要反转部分头节点的前一个结点
             pre = pre.next;
        }
         ListNode cutLast = pre;
         for (int i = 0; i < right - left + 1; i++) {
             //获得需反转部分的尾
             cutLast = cutLast.next;
        }
         
         //这里需记录不反转的后一部分链表
         ListNode next = cutLast.next;
         //记录反转的头
         ListNode cutHead = pre.next;
         
         //截断链表
         pre.next = null;
         cutLast.next = null;
         
         ListNode[] node = reverse(cutHead, cutLast);
         
         //拼接链表
         pre.next = node[0];
         node[1].next = next;
         
         return hair.next;
    }
     
     //尾插反转,参考上题
     public ListNode[] reverse(ListNode head, ListNode tail) {
    ListNode cur = null;
         ListNode pre = head;
         while (cur != null) {
        ListNode next = pre.next;
             pre.next = cur;
             cur = pre;
             pre = next;
        }
         return new ListNode[]{tail, head};
    }
 }

这道题当然还有更方便的解法,有些步骤可能会很啰嗦,但这样方便理解为后面做铺垫。

那么我们再来看一道困难的题目--K 个一组翻转链表

给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。

k 是一个正整数,它的值小于或等于链表的长度。

如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

进阶:

你可以设计一个只使用常数额外空间的算法来解决此问题吗? 你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

 

有了上面一道题目的铺垫这道题就容易多了,只需注意当剩余结点数小于k时直接返回即可。

 public class Solution {
     public ListNode reverseKGroup(ListNode head, int k) {
         ListNode hair = new ListNode(-1);
         hair.next = head;
         ListNode pre = hair;
         //迭代的是head,pre只保存拼接头,认真体会
         while (head != null) {
             ListNode tail = head; //反转尾
             for (int i = 1; i < k; i++) {
                 tail = tail.next;  //从1开始
                 if (tail == null) {  //不足k时直接返回
                     return hair.next;
                }
            }
             //记录并切割
             ListNode next = tail.next;
             pre.next = null;
             tail.next = null;
             
             ListNode[] node = reverse(head, tail);
             
             //拼接
             pre.next = node[0];
             node[1].next = next;
             
             //替换新pre和head
             pre = node[1];
             head = next;
        }
         return hair.next;
    }
     
     //参考上题
     public ListNode[] reverse(ListNode head, ListNode tail) {
    ListNode cur = null;
         ListNode pre = head;
         while (pre != null) {
        ListNode next = pre.next;
             pre.next = cur;
             cur = pre;
             pre = next;
        }
         return new ListNode[]{tail, head};
    }
 }

到此反转链表总结的也差不多了,这几道已经写过很多遍了,但每每遇到还是写不出来,每次都很混乱。今天总结一下,仅为个人面试,不是最优解,希望和大家多多交流一起成长。

 

posted @ 2021-05-01 23:07  莫泰  阅读(232)  评论(0)    收藏  举报