LeetCode -- 链表反转系问题

207 阅读3分钟

今天补一下昨天没写的博客。

今天是记录leetcode刷题的第六天,今天的问题是一个经常被问到的面试题--链表反转。

我们循序渐进,从易->难,对今天做的题及逆行一个排序:

  • 反转链表
  • 指定区间反转链表
  • 链表每k个一组反转

反转链表

要求如下

给定一个单链表的头结点pHead,长度为n,反转该链表后,返回新链表的表头。

作为最简单的反转链表,只要思路对了就很容易实现,而思路就是双指针

1、我们每次都记住第i个指针第i+1个指针,然后翻转,

2、再将双指针后移一位,

3、循环前面的步骤,直到i+1指向的指针为空结束。

这个实现起来比较简单,并没有什么难点,所以直接放代码了

public ListNode ReverseList(ListNode head) {
    // 双指针 ,一个指针指向头节点,一个指针指向头节点的下一节点
    // null
    if(head == null || head.next == null) {
        return head;
    }
    ListNode pre = head;
    ListNode next = head.next;
    //断开
    pre.next = null;
    while (next != null){
        ListNode temp = next.next;
        // 断开连接,并反向  1 --> 2 --> 3 --> 4  ==>   null <-- 1 <-- 2
        next.next = pre;
        pre = next;
        next = temp;
    }
    return pre;
}
static class ListNode {
    int val;

    ListNode next;

    public ListNode(int x) {
        val = x;
    }
}

指定区间

要求如下

将一个节点数为 size 链表 m 位置到 n 位置之间的区间反转,要求时间复杂度O(n),空间复杂度 O(1)。

例如:

给出的链表为 1 → 2 → 3 → 4 → 5 → NULL, m=2,n=4

返回 1 → 4 → 3 → 2 → 5 → NULL.

指定区间后,我们就需要找到起点和终点,然后将此链表“孤立”,然后反转此链表,然后再把链表拼接起来,这样的话,我们就需要起点的前置节点和终点的后置节点。

画个图更容易明白

1、找到起点和终点,前置节点和后置节点。

image.png

2、断开连接

image.png

3、翻转

image.png

4、连接

image.png

注意,在实际编程过程中,不完全按照上图的规则,我们需要随机应变,画图只是为了好理解,需要时刻注意节点的移动。

代码如下:

public ListNode reverseBetween(ListNode head, int m, int n) {
    if (head == null || head.next == null || m == n) {
        return head;
    }
    //找到头和前置节点
    ListNode pre = null;
    ListNode left = head;
    while (--m > 0) {
        pre = left;
        left = pre.next;
    }
    //找到尾和后置节点
    ListNode right = head;
    while (--n > 0) {
        right = right.next;
    }
    ListNode next = right.next;

    // 断开与后面相连的部分,也是孤立前面的链表,这样就形成了反转前面的链表,
    right.next = null;

    // 翻转 left -> right,同时拼接最后面的部分
    ListNode cur = left.next;
    // 断开,而因为此时left为末尾,下一个就是next
    left.next = next;
    while (cur != null) {
        ListNode temp = cur.next;
        cur.next = left;
        left = cur;
        cur = temp;
    }

    // 拼接
    if(pre == null){
        // pre 为null,则left为头,直接返回
        return left;
    }
    // pre不为null,则代表有前置节点,则拼接起来
    pre.next = left;
    return head;
}
static class ListNode {
    int val;

    ListNode next;

    public ListNode(int x) {
        val = x;
    }
}

K个一组

有了上面做铺垫,相信这道题对你来说已经不算很难了,只需要对上述代码稍加处理就可以完成。

代码如下:

public ListNode reverseKGroup(ListNode head, int k) {
    // write code here

    int size = getSize(head);
    // 记录当前旋转的是第几组
    int start = 0, end = k - 1;
    while (end < size){
        // 旋转
        head = reverseBetween(head,start,end);
        // 下一组
        start = start + k;
        end = end + k;
    }
    return head;
}

private ListNode reverseBetween(ListNode node, int start, int end) {
    // K
    int k = end - start;

    // 找到头和前置节点
    ListNode pre = null;
    ListNode head = node;
    while(start-- > 0){
        pre = head;
        head = pre.next;
    }

    // 找到尾
    ListNode tail = head;
    while (k-- > 0){
        tail = tail.next;
    }
    // 后置节点
    ListNode next = tail.next;

    // 断开
    tail.next = null;

    // 翻转 head -> tail
    ListNode cur = head.next;
    head.next = next;
    while (cur != null){
        ListNode temp = cur.next;
        cur.next = head;
        head = cur;
        cur = temp;
    }
    // pre -> head
    if(pre == null){
        return head;
    }
    pre.next = tail;
    return node;
}

private int getSize(ListNode head) {
    int size = 0;
    while (head != null) {
        size++;
        head = head.next;
    }
    return size;
}

static class ListNode {
    int val;

    ListNode next;

    public ListNode(int x) {
        val = x;
    }
}

传送门

多加练习,未来更好。