链表应用 II

链表应用 II

应用 2:Leetcode 25. K 个一组翻转链表

题目

25. K 个一组翻转链表

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

分析

这里,我们以前面题目中的用例,来说明算法的步骤。

为了避免讨论边界条件,这里,我们使用一个 \(dummy\) 节点指向 \(head\)

首先,使用三个指针用于遍历主链表时记录子链表在主链表中的位置:

  • \(P_0\) :记录子链表的首节点的前一个节点;

  • \(P_1\) 记录子链表的首节点;

  • \(tail\) 记录子链表的尾节点。

然后,我们遍历主链表,当tail向后移动k步之后,tail所指的元素就是子链表的尾节点,如下图所示:

image

image

image

此时,\(P_1\) 记录子链表的首节点 \(1\)\(tail\) 指向待倒序的子链表的尾节点 \(2\),同时,用 \(post\) 指针记录子链表的下一个节点:

image

然后,我们对子链表进行倒序,我们定义一个两个指针:

  • \(P_3\) 记录子链表的当前节点的前一个节点;

  • \(P_4\) 记录子链表的当前节点;

对子链表进行倒序操作:

image

image

image

image

image

image

子链表倒序的终止条件是 \(P_3\) 移动到子链表的 \(tail\) 节点;

image

倒序完成后,重新将 \(P_1\) 指向倒序后的子链表的首节点 \(2\)\(tail\) 指向倒序后的子链表的尾节点 \(1\),如下图所示:

image

将前驱指针 \(P_0\) 所指向的节点的 \(next\) 指针重新指向新的子链表首节点:

image

将子链表的尾结点的 \(next\) 指针重新指向主链表后驱节点 \(post\)

image

重新指针 \(P_0\) 指向下一个子链表的前驱节点(即当前子链表的尾结点),将 \(P_1\) 指向下一个子链表的首节点:

image

因此,我们可以总结出算法步骤如下:

  • 使用一个指针 \(tail\),先让它指向 \(dummy\) 节点,并用它来遍历主链表,使用如下四个指针,记录子链表在主链表中的位置:

    • \(P_0\):前驱指针,用于记录子链表首节点的前一个节点;

    • \(P_1\):记录子链表的首节点;

    • \(tail\):记录子链表的尾节点;

    • \(post\):后驱指针,用于记录子链表尾节点的后一个节点;

  • 指针 \(tail\) 每移动 \(k\) 步:

    • \(P_1\)\(tail\) 所在的子链表进行倒序操作;

    • 然后,重新将 \(tail\) 指向倒序后的子链表尾节点,将 \(head\) 指向倒序后的子链表首节点;

    • 将倒序后的子链表重新接入主链表,即:

      • 将前驱节点 \(P_0\)\(next\) 指针指向的节点指向倒序后的子链表头节点 \(P1\)

      • 将倒序后的子链表尾节点 \(tail\)\(next\) 指针指向的节点指向后驱节点 \(post\)

    • 重新将 \(P_0\) 指向当前子链表的尾节点 \(tail\),即下一个子链表的前驱节点;

    • 重新将 \(P_1\) 指向当前子链表的尾节点 \(tail\) 的下一个节点,即下一个子链表的头节点;

  • 若指针 \(tail\) 移动的步数小于 \(k\) 步,则直接返回;

代码实现

class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
        int n = listLength(head);
        if (n <= 1) {
            return head;
        }
        ListNode dummy = new ListNode(-1);
        dummy.next = head;
        // 记录子链表的前驱节点
        ListNode subListPrev = dummy;
        ListNode subListHead = head;
        while (subListHead != null) {
            // 从前驱节点开始计算子链表的长度
            ListNode subListTail = subListPrev;
            // 查看剩余部分的长度是否大于等于 k
            for (int count = 0; count < k; count++) {
                subListTail = subListTail.next;
                if (subListTail == null) {
                    return dummy.next;
                }
            }

            // 记录子链表的后驱节点
            ListNode subListPost = subListTail.next;
            // 子链表倒序
            ListNode [] nodes = reverse(subListHead, subListTail);
            subListHead = nodes[0];
            subListTail = nodes[1];

            // 子链表的前驱节点指向子链表的头部
            subListPrev.next = subListHead;
            // 子链表的尾节点指向子链表的后驱节点
            subListTail.next = subListPost;
            // 更新前驱节点的位置
            subListPrev = subListTail;
            // 更新子链表的头节点的位置
            subListHead = subListTail.next;
        }

        return dummy.next;
    }

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

    private ListNode [] reverse(ListNode start, ListNode end) {
        ListNode p1 = null, p2 = start;
        ListNode temp;
        while (p1 != end) {
            temp = p2.next;
            p2.next = p1;
            p1 = p2;
            p2 = temp;
        }
        return new ListNode[]{end, start};
    }
}

应用2:Leetcode 234. 回文链表

题目

234. 回文链表

分析

方法一:数组

思路:

  • 将链表的值复制到数组中;

  • 使用双指针判断是否是回文。

方法二:递归

略。

方法三:快慢指针

算法个步骤:

  • 找到前半部分链表的尾节点;

  • 反转后半部分链表;

  • 判断两个子链表是否回文;

  • 恢复后面一个子链表;

  • 返回结果。

代码实现

方法一

class Solution:
    def isPalindrome(self, head: Optional[ListNode]) -> bool:
        array = list()
        current = head
        while current:
            array.append(current.val)
            current = current.next
        return array == array[::-1]

复杂度:

  • 时间复杂度:\(O(n)\)

  • 空间复杂度:\(O(n)\)

方法二

class Solution:
    def __init__(self):
        self.front_pointer = None

    def isPalindrome(self, head: Optional[ListNode]) -> bool:
        self.front_pointer = head
        return self.check(head)

    def check(self, head):
        if head:
            if not self.check(head.next):
                return False

            if head.val != self.front_pointer.val:
                return False
            self.front_pointer = self.front_pointer.next
        return True

复杂度:

  • 时间复杂度:\(O(n)\)

  • 空间复杂度:\(O(n)\)

方法三

class Solution {
    public boolean isPalindrome(ListNode head) {
        ListNode preNode = getMidOfList(head);
        ListNode tailNode = reverseList(preNode.next);
        boolean result = true;
        ListNode p1 = head, p2 = tailNode;
        while (p1 != null && p2 != null) {
            if (p1.val != p2.val) {
                result = false;
                break;
            }
            p1 = p1.next;
            p2 = p2.next;
        }
        preNode.next = reverseList(tailNode);
        return result;
    }

    private ListNode reverseList(ListNode head) {
        if (head == null) {
            return null;
        }
        ListNode p1 = null, p2 = head;
        ListNode temp = null;
        while (p2 != null) {
            temp = p2.next;
            p2.next = p1;
            p1 = p2;
            p2 = temp;
        }
        return p1;
    }

    private ListNode getMidOfList(ListNode head) {
        ListNode slow = head, fast = head.next;
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }
        return slow;
    }
}

复杂度:

  • 时间复杂度:\(O(n)\)

  • 空间复杂度:\(O(1)\)

应用3:Leetcode 143. 重排链表

题目

143. 重排链表

给定一个单链表 L 的头节点 head ,单链表 L 表示为:
\(L_0 → L_1 → \cdots → L_{n - 1} → L_n\)
请将其重新排列后变为:
$L_0 → L_n → L_1 → L_{n - 1} → L_2 → L_{n - 2} → \cdots $
不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

示例 1:

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

分析

假设链表长度为 \(n\),比较朴素的思路是,从 \(\frac{n}{2}\) 处,把链表从中点,分割为两个子链表将后半部分的子链表倒序,然后再按照题目要求重新拼接为一个新链表即可。

其中,寻找链表中点的方法,可以使用快慢指针的方式查找,当快指针到达链表末尾,慢指针刚好到达链表左半段的最后一个节点。

注意,如果链表长度为奇数时,前半段会多一个节点,只需要将其连接到新链表的末尾即可。

代码实现

class Solution {
    public void reorderList(ListNode head) {
        ListNode mid = getMidNode(head);
        // 将链表分成两个子链表
        ListNode tailNode = reverse(mid.next);
        mid.next = null;

        // 将两个子链表按要求合并
        ListNode dummy = new ListNode(-1);
        dummy.next = head;
        ListNode p = dummy;
        ListNode p1 = head, p2 = tailNode;
        while (p1 != null && p2 != null) {
            p.next = p1;
            p = p.next;
            p1 = p1.next;

            p.next = p2;
            p = p.next;
            p2 = p2.next;
        }

        // 如果节点为奇数,需要将剩余节点添加到末尾
        if (p1 != null) {
            p.next = p1; 
        }
    }

    private ListNode getMidNode(ListNode head) {
        ListNode slow = head, fast = head.next;
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }
        return slow;
    }

    private ListNode reverse(ListNode head) {
        ListNode p1 = null, p2 = head;
        ListNode temp;
        while (p2 != null) {
            temp = p2.next;
            p2.next = p1;
            p1 = p2;
            p2 = temp;
        }
        return p1;
    }
}

posted @ 2023-04-12 23:17  LARRY1024  阅读(12)  评论(0编辑  收藏  举报