反转链表| 8月更文挑战

753 阅读3分钟

前言

反转链表相关的问题,在面试中出显示频率很高, 因此总结下几道链表反转相关的问题

  1. NC78 反转链表(简单)
  2. NC21 链表内指定区间反转(中等)
  3. NC50 链表中的节点每k个一组翻转(中等)

1. 反转链表

描述 : 输入一个链表,反转链表后,输出新链表的表头。

思路分析:

  • 迭代实现 使用一个前置节点 pre 和 当前节点 cur 循环遍历更新即可
  • 递归实现 定义递归函数,处理 base case , 对当前节点的 next 节点调用递归函数处理即可

AC 代码:

迭代版本: 时间复杂度 O(n) 空间复杂度 O(1)

public ListNode reverseList(ListNode head) {
    if (head == null || head.next == null) return head;
    
    ListNode pre = null;
    ListNode next;

    while (head != null) {
        next = head.next;
        head.next = pre;
        pre = head;
        head = next;
    }
    return pre;
}

递归版本: 时间复杂度 O(n) 空间复杂度 O(n)

public ListNode reverseList(ListNode head) {
    if (head == null || head.next == null) return head;
    
    ListNode res = reverseList(head.next);
    head.next.next = head;
    head.next = null;

    return res;
}

2. 链表内指定区间反转

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

思路分析: 首先遍历 链表找到 left 位置的 start 节点 和 right + 1 位置的 tail 节点, 修改下 反转链表I 中的终止条件即可, 记录 left 的前置节点 leftPreNode , 最后连接到一起

Tip : 链表相关的问题 一般情况下都会添加一个虚拟的头节点;

下面实现代码中,将寻找指定位置节点的代码 和 反转指定区间节点的代码 单独放到了一个方法中, 实际上一次遍历可以完成这道题目, 处于可读性的考虑,将其中的逻辑抽出

AC 代码:

public ListNode reverseBetween(ListNode head, int left, int right) {
    ListNode dumpNode = new ListNode(-1);
    dumpNode.next = head;

    ListNode leftPreNode = findNode(dumpNode, left);
    ListNode leftNode = leftPreNode.next;
    ListNode rightNextNode = findNode(dumpNode, right + 2);

    leftPreNode.next = reverse(leftNode, rightNextNode);
    leftNode.next = rightNextNode;

    return dumpNode.next;
}

ListNode findNode(ListNode head, int index) {
    int count = 1;
    while (count != index) {
        head = head.next;
        count++;
    }
    return head;
}

ListNode reverse(ListNode head, ListNode tail) {
    if (head == tail || head.next == tail) return head;
    
    ListNode pre = null;
    ListNode next;
    while (head != tail) {
        next = head.next;
        head.next = pre;
        pre = head;
        head = next;
    }
    return pre;
}

3. 链表中的节点每k个一组翻转

描述 :将给出的链表中的节点每 k 个一组翻转,返回翻转后的链表; 如果链表中的节点数不是 k 的倍数,将最后剩下的节点保持原样; 你不能更改节点中的值,只能更改节点本身。

输入: head = [1,2,3,4,5], k = 3
输出: [3,2,1,4,5]

思路分析: 使用 链表内指定区间反转 的 reverse(ListNode head, ListNode tail) 函数辅助; 定义递归函数 reverseKGroup 返回 反转之后的头节点, 反转一个区间之后 将该区间的尾节点指向 下一区间的头节点即可

AC 代码:

    public ListNode reverseKGroup (ListNode head, int k) {
        ListNode cur = head;
        int count = 0;
        while (cur != null) {
            cur = cur.next;
            count++;
            if (count == k) {
                break;
            }
        }
        if (count != k) {
            return head;
        }

        ListNode ans = reverse(head, cur);
        head.next = reverseKGroup(cur, k);
        return ans;
    }