算法刷题——链表反转相关问题

225 阅读4分钟

本文已参与「新人创作礼」活动, 一起开启掘金创作之路。

本文的几个问题都是非常经典的链表反转问题,这里主要采用双指针的方式来解决这些问题,这些问题都来自牛客网 面试必刷TOP101

反转链表

解题思路:

  1. 使用栈:这里最先想到的就是使用栈,栈的最基本特征就是先进后出,将链表全部放入栈中,再取出就反转了链表。

  2. 双指针:然后就是双指针,我们可以使用cur指向当前待反转节点,和使用pre指向已经翻转节点的头节点,再将cur指向pre成为已经反转链表的头节点,当cur指向null的时候就是说明,所有的链表反转完成。然后返回 pre。 整个过程图解如下:

BM1-1.png

如果直接将curpre互换的话会造成前面的链表丢失,所以从cur换到pre的过程我们需要一个临时的指针temp指针指向cur.next,当cur指向pre的时候我们可以通过temp指针找回下一个需要反转的节点,让cur指向。

BM1-2.png

这样就可以使用下面这段代码进行反转

ListNode temp = cur.next;
cur.next = pre;
pre = cur;
cur = temp;

完整答案:

public class Solution {
    public ListNode ReverseList(ListNode head) {
        if(head == null){
            return head;
        }
        ListNode cur = head;
        ListNode pre = null;
        while(cur != null){
            ListNode temp = cur.next;
            cur.next = pre;
            pre = cur;
            cur = temp;
        }
        return pre;
    }
}

这里我们可以看待两个指针一直再指向不同的节点,来进行重新指向的问题。

给定区间链表反转

解题思路

  1. 和上面的区别就是:上面的是要将整个链表反转过来,而这个题目只要将中间部分反转。这里有个问题就是,这里我们会想到记录好区间的前一个节点和后一个节点。通过上面的反转,然后直接指向就可以了。
  2. 我们还是可以使用双指针的方式,使用pre指向待反转链表的前一个节点,cur初始链表中要反转区间的第一个节点,pre指向cur的后一个节点cur.next这里我们还是使用temp这个临时节点来记录。

BM2-1.png

首先我们将cur指向temp.next

然后我们将temp指向pre.next

最后我们将pre指向temp

BM2-2.png

一个细节: 为什么在第二步的时候我们不直接temp指向cur呢?如果只是第一张图可能觉得好像没有问题,但是我们要保证的是每次cur后面的节点插入到prepre.next中间的节点之中去,所以我们需要像第二步那样指向。而如果按照我们这个问题来,那将会有多个节点指向cur

因此我们的核心代码就出现了

ListNode temp = cur.next;
cur.next = temp.next;
temp.next = pre.next;
pre.next = temp;

完整答案:

在上面分析的基础上,我们还需要添加一个头节点,如果反转区间为0开始那就没有前置节点,所以我们需要一个添加一个头节点

public class Solution {
    public ListNode reverseBetween (ListNode head, int m, int n) {
        // write code here
        ListNode res = new ListNode(-1);
        res.next = head;

        ListNode pre = res;
        ListNode cur = head;
        //找到区间开始节点
        for(int i = 1; i < m; i++){
            pre = cur;
            cur = cur.next;
        }

        for(int i = m; i < n; i++){
            ListNode temp = cur.next;
            cur.next = temp.next;
            temp.next = pre.next;
            pre.next = temp;
        }
        return res.next;
    }
}

对比

和上面一题做对比,两题都是双指针解决这个问题,但是思路是不一样的,前一题的解决方法中,指针curpre一直在转换节点,本题的这两个指针指向的节点一直没有转变,看似相似都是双指针,但是双指针的运用是完全不一样的。

按组翻转

解题思路

这题的核心代码部分和第一种是一样的,但是运用到了嵌套的思维。 那就是每翻转一组,我就将剩下的头和尾再翻转一次,所以这样判断循环结束的条件就不是cur == null

而是cur是否为下一组的开头,这里我们就设置它为tail

BM3-1.png

这里只标注了第一组的tail,使用嵌套每做完一组就标记下一组的tail

完整代码

public class Solution {
    public ListNode reverseKGroup (ListNode head, int k) {
        // write code here
        ListNode tail = head;
        for(int i = 0; i < k; i++){
            if(tail == null){
                return head;
            }
            tail = tail.next;
        }
        //第一部分的核心代码
        ListNode pre = null;
        ListNode cur = head;
        while(cur != tail){
            ListNode temp = cur.next;
            cur.next = pre;
            pre = cur;
            cur = temp;
        }
        //使用嵌套
        head.next = reverseKGroup(tail,k);
        return pre;
    }
}