一文搞懂所有的反转链表题

112 阅读3分钟

相关题目

  1. leetcode.cn/problems/re…
  2. leetcode.cn/problems/re…
  3. leetcode.cn/problems/re…

反转链表的原型

题目如下:

image.png

拆解反转的过程:
1->2->3->4->5
1 2->3->4->5
1<-2 3->4->5
1<-2<-3 4->5
1<-2<-3<-4 5
1<-2<-3<-4<-5

可以看出反转的过程中有两个链表:

  1. 已经反转好的链表

已经反转的链表okHead,初始化应该是null,然后逐个反转过程中,okHead一直更新。

  1. 剩下未反转的链表

剩下未反转链表remainHead,初始化就是原来的链表头节点head,随着节点逐个反转,remainHead也一直在更新。

具体反转过程看以下代码的注解

public ListNode reverseList(ListNode head) {
    // 已经反转的链表okHead,初始化应该是null
    ListNode okHead = null;

    // 剩下未反转链表remainHead,初始化就是原来的链表头节点head
    ListNode remainHead = head;

    // 还有剩下的未反转的节点,就继续反转
    while (remainHead != null) {
        // 暂时保存下一个待反转的节点,防止找不到了(因为当前节点next要指向已经反转好的链表)
        ListNode nextHead = remainHead.next;
        
        // 当前节点要指向反转好的链表头,并把自己当作新的已反转链表头
        remainHead.next = okHead;
        okHead = remainHead;
        
        // 把刚才暂时保存的节点当作未反转链表的头节点
        remainHead = nextHead;
    }
    return okHead;
}

反转链表变形一

image.png

把反转链表当作一个已经拥有的功能,只要考虑怎么利用已有功能来达到题目想要的效果。
找到左边节点的前一个节点leftPre,和右边节点的下一个节点rightNext,以及反转后的头节点leftNode、尾节点rightNode,就可以把他们连接起来。

leftPre.next=rightNode;
leftNode.next=rightNext;

具体看代码

public ListNode reverseBetween(ListNode head, int left, int right) {
    // 简化left=1这种类型,直接搞一个虚拟的头节点(链表中常用技巧)
    ListNode headHead = new ListNode();
    headHead.next = head;

    ListNode cur = headHead;
    ListNode leftPre = null;
    ListNode leftNode = null;
    ListNode rightNode = null;
    ListNode rightNext = null;

    for (int i = 0; i <= right; i++) {
        if (i == left - 1) {
            leftPre = cur;
            leftNode = cur.next;
        }
        if (i == right) {
            rightNode = cur;
            rightNext = cur.next;
        }
        cur = cur.next;
    }
    // 先解开链子
    leftPre.next = null; //这一步非必须,这里是为了好理解
    rightNode.next = null;  //这一步必须,因为要判断反转的最后一个节点
    
    // 反转
    reverseList(leftNode);

    // 连接起来
    leftPre.next = rightNode;
    leftNode.next = rightNext;
    return headHead.next;
}

反转链表变形二

image.png

注意审题,K个一组,不满k个的就保持原样

把反转链表当作一个已经拥有的功能,只要考虑怎么利用已有功能来达到题目想要的效果。 这里面就有两个问题:

  1. 按组反转后怎么连接起来 首先要有一个变量kPre保存前一组链表的最后一个。
    当前这组链表反转完之后返回的是newHead,连接到上一组链表中:kPre.next=newHead
    那么当你要反转下一组链表的时候,要怎么更新kPre呢,很简单,在反转当前组之前,把第一个节点保存下来记作kLast,这个节点就是我们要更新成为的kPre。

  2. 不够k个的,怎么保持原顺序

每一组在反转之前,看下够不够k个元素,不够的话就不需要反转。这时候不影响时间复杂度,仍然是O(N)。

具体看代码注释

public ListNode reverseKGroup(ListNode head, int k) {
    // 为了保持一致性,加一个虚拟的头结点
    ListNode xNode = new ListNode(-1);
    xNode.next = head;
    //初始化kPre和kLast
    ListNode kPre = xNode;
    ListNode kLast = kPre.next;

    while (kLast != null) { //如果下一个节点是空的就不需要反转了
        // 往后走k步,如果不为空,就说明够k个元素
        ListNode nextK = kPre;
        for (int i = 0; i < k; i++) {
            nextK = nextK.next;
            if (nextK == null) {
                break;
            }
        }
        if (nextK != null) {
            //够k个元素,执行标砖的反转
            ListNode okHead = null;
            ListNode remainHead = kPre.next;
            for (int i = 0; i < k; i++) {
                ListNode nextHead = remainHead.next;
                remainHead.next = okHead;
                okHead = remainHead;
                remainHead = nextHead;
            }

            // 反转完毕之后 连接+更新kPre和kLast
            kLast.next = remainHead; // 连接到下一个
            kPre.next = okHead; // 上一组连接自己
            kPre = kLast; // 更新下一组前一个节点
            kLast = remainHead; // 更新下一组的最后一个节点
        }else{
            //不够k个,直接退出
            break;
        }
    }
    return xNode.next;
}