2023-04-12 23:17阅读: 16评论: 0推荐: 0

链表应用 II

链表应用 II

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

题目

25. K 个一组翻转链表

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

分析

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

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

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

  • P0 :记录子链表的首节点的前一个节点;

  • P1 记录子链表的首节点;

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

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

image

image

image

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

image

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

  • P3 记录子链表的当前节点的前一个节点;

  • P4 记录子链表的当前节点;

对子链表进行倒序操作:

image

image

image

image

image

image

子链表倒序的终止条件是 P3 移动到子链表的 tail 节点;

image

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

image

将前驱指针 P0 所指向的节点的 next 指针重新指向新的子链表首节点:

image

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

image

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

image

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

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

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

    • P1:记录子链表的首节点;

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

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

  • 指针 tail 每移动 k 步:

    • P1tail 所在的子链表进行倒序操作;

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

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

      • 将前驱节点 P0next 指针指向的节点指向倒序后的子链表头节点 P1

      • 将倒序后的子链表尾节点 tailnext 指针指向的节点指向后驱节点 post

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

    • 重新将 P1 指向当前子链表的尾节点 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 表示为:
L0L1Ln1Ln
请将其重新排列后变为:
L0LnL1Ln1L2Ln2
不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

示例 1:

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

分析

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

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

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

代码实现

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;
}
}

本文作者:LARRY1024

本文链接:https://www.cnblogs.com/larry1024/p/17311327.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   LARRY1024  阅读(16)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起
  1. 1 404 not found REOL
404 not found - REOL
00:00 / 00:00
An audio error has occurred.