🌈你好呀!我是 山顶风景独好
💕欢迎来到我的博客,很高兴能够在这里和您见面!
💕希望您在这里可以感受到一份轻松愉快的氛围!
💕这里不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。
🚀 欢迎一起踏上探险之旅,挖掘无限可能,共同成长!
138.【中等】随机链表的复制
题目描述
- 给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。
- 构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。
- 例如,如果原链表中有 X 和 Y 两个节点,其中 X.random --> Y 。那么在复制链表中对应的两个节点 x 和 y ,同样有 x.random --> y 。
- 返回复制链表的头节点。
- 用一个由 n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index] 表示:
- val:一个表示 Node.val 的整数。
- random_index:随机指针指向的节点索引(范围从 0 到 n-1);如果不指向任何节点,则为 null 。
- 你的代码 只 接受原链表的头节点 head 作为传入参数。
示例 1:
- 输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
- 输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]
示例 2:
- 输入:head = [[1,1],[2,1]]
- 输出:[[1,1],[2,1]]
示例 3:
- 输入:head = [[3,null],[3,0],[3,null]]
- 输出:[[3,null],[3,0],[3,null]]
/*
// Definition for a Node.
class Node {
int val;
Node next;
Node random;
public Node(int val) {
this.val = val;
this.next = null;
this.random = null;
}
}
*/
class Solution {
public Node copyRandomList(Node head) {
// 如果原链表为空,则直接返回null
if(head == null) return null;
// 定义一个指针cur,用于遍历原链表
Node cur = head;
// 1. 复制各节点,并构建拼接链表
// 遍历原链表,在每个节点后面复制一个新的节点,并链接到原节点的next
while(cur != null) {
// 创建一个新节点tmp,其值与当前节点cur相同
Node tmp = new Node(cur.val);
// 将新节点tmp的next指向当前节点cur的下一个节点
tmp.next = cur.next;
// 将当前节点cur的next指向新节点tmp,实现新旧节点的拼接
cur.next = tmp;
// 移动cur到下一个节点
cur = tmp.next;
}
// 2. 构建各新节点的 random 指向
// 再次遍历原链表(实际上是新旧节点拼接的链表),设置新节点的random指针
cur = head;
while(cur != null) {
// 如果当前节点cur的random指针不为空
if(cur.random != null)
// 将当前节点cur的下一个节点(即新节点)的random指向原节点cur的random指针的下一个节点(即对应的新节点)
cur.next.random = cur.random.next;
// 移动cur到下一个新旧节点对
cur = cur.next.next;
}
// 3. 拆分两链表
// 将新旧节点拼接的链表拆分为两个独立的链表:原链表和新链表
cur = head.next; // cur指向新链表的头节点
Node pre = head, res = head.next; // pre用于遍历原链表,res用于保存新链表的头节点
// 遍历链表,拆分新旧节点
while(cur.next != null) {
// 将原链表的当前节点pre的next指向下一个新节点
pre.next = pre.next.next;
// 将新链表的当前节点cur的next指向下一个新节点
cur.next = cur.next.next;
// 移动pre和cur到下一个节点
pre = pre.next;
cur = cur.next;
}
// 单独处理原链表的尾节点,将其next置为null
pre.next = null;
// 返回新链表的头节点
return res;
}
}
92.【中等】反转链表 II
题目描述
给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。
示例 1:
- 输入:head = [1,2,3,4,5], left = 2, right = 4
- 输出:[1,4,3,2,5]
示例 2:
- 输入:head = [5], left = 1, right = 1
- 输出:[5]
class Solution {
// 反转从位置 left 到 right 的链表部分
public ListNode reverseBetween(ListNode head, int left, int right) {
// 创建一个哑节点dummy,它的next指向head,这样可以避免处理头节点为null或left为1的特殊情况
ListNode dummy = new ListNode(0, head);
// p0指针用于找到需要反转部分的起始位置的前一个节点
ListNode p0 = dummy;
// 移动p0指针到left位置的前一个节点
for (int i = 0; i < left - 1; ++i)
p0 = p0.next;
// pre指针用于反转过程中的前一个节点
ListNode pre = null;
// cur指针指向当前需要处理的节点
ListNode cur = p0.next;
// 反转从left到right的链表部分
for (int i = 0; i < right - left + 1; ++i) {
// nxt指针暂存cur的下一个节点,因为下一步cur.next会被修改
ListNode nxt = cur.next;
// 将cur的next指针指向前一个节点,实现反转
cur.next = pre;
// pre指针向后移动
pre = cur;
// cur指针向后移动
cur = nxt;
}
// 将p0的next指针指向反转后的部分的最后一个节点(即原来的right位置的节点)
// 这一步很重要,因为它连接了反转部分和原链表的其他部分
p0.next.next = cur;
// 将p0的next指针指向反转后的部分的头节点(即原来的left位置的节点)
// 这一步将反转部分与原链表的其他部分连接起来
p0.next = pre;
// dummy.next是反转后整个链表的头节点,所以返回它
return dummy.next;
}
}
25.【困难】K 个一组翻转链表
题目描述
- 给你链表的头节点 head ,每 k 个节点一组进行翻转,请你返回修改后的链表。
- k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。
- 你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。
示例 1:
- 输入:head = [1,2,3,4,5], k = 2
- 输出:[2,1,4,3,5]
示例 2:
- 输入:head = [1,2,3,4,5], k = 3
- 输出:[3,2,1,4,5]
class Solution {
// 反转链表中每k个节点
public ListNode reverseKGroup(ListNode head, int k) {
// 创建一个哑节点(hair node),方便处理头节点反转的情况
ListNode hair = new ListNode(0);
hair.next = head;
// pre指针用于指向当前需要反转的k个节点的前一个节点
ListNode pre = hair;
// 当头节点不为空时,循环进行反转
while (head != null) {
// tail指针用于指向当前需要反转的k个节点的最后一个节点
ListNode tail = pre;
// 查看剩余部分长度是否大于等于 k
// 如果剩余长度小于k,则无需再反转,直接返回结果
for (int i = 0; i < k; ++i) {
tail = tail.next;
if (tail == null) {
// 如果tail为null,说明剩余节点不足k个,直接返回结果
return hair.next;
}
}
// nex指针用于保存tail的下一个节点,即下一个需要反转的k个节点的头节点
ListNode nex = tail.next;
// 调用myReverse方法反转当前k个节点,并返回反转后的头尾节点
ListNode[] reverse = myReverse(head, tail);
// 更新head为反转后的头节点
head = reverse[0];
// 更新tail为反转后的尾节点
tail = reverse[1];
// 把子链表重新接回原链表
// pre.next指向反转后的头节点
pre.next = head;
// tail.next指向下一个需要反转的k个节点的头节点
tail.next = nex;
// pre指针移动到当前反转的k个节点的尾节点
pre = tail;
// head指针移动到下一个需要反转的k个节点的头节点
head = tail.next;
}
// 返回反转后的链表头节点
return hair.next;
}
// 反转链表中的节点,从head到tail(不包括tail)
public ListNode[] myReverse(ListNode head, ListNode tail) {
// prev指针用于指向反转后的前一个节点,初始为tail的下一个节点
ListNode prev = tail.next;
ListNode p = head;
while (prev != tail) {
// nex指针用于保存p的下一个节点
ListNode nex = p.next;
// 反转p的指向
p.next = prev;
// prev向后移动
prev = p;
// p向前移动
p = nex;
}
// 返回反转后的头尾节点数组
return new ListNode[]{tail, head};
}
}
19.【中等】删除链表的倒数第 N 个结点
题目描述
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
示例 1:
- 输入:head = [1,2,3,4,5], n = 2
- 输出:[1,2,3,5]
示例 2:- 输入:head = [1], n = 1
- 输出:[]
示例 3:- 输入:head = [1,2], n = 1
- 输出:[1]
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
// 定义一个方法,用于从链表的末尾删除第n个节点
public ListNode removeNthFromEnd(ListNode head, int n) {
// 创建一个虚拟头节点dummy,其值为0,并指向原链表的头节点head
// 这样做的目的是为了方便处理删除头节点的情况
ListNode dummy = new ListNode(0, head);
// 定义两个指针first和second,都初始化为链表的头节点
// first指针用于先走n步,以确定与second指针之间的间隔
ListNode first = head;
ListNode second = dummy;
// first指针先走n步
for (int i = 0; i < n; ++i) {
first = first.next;
}
// 当first指针到达链表末尾时,second指针指向的下一个节点即为要删除的节点的前一个节点
// 通过移动first和second指针,保持它们之间的间隔始终为n
while (first != null) {
first = first.next;
second = second.next;
}
// 删除second指针指向的下一个节点(即要删除的节点)
// 通过将second.next指向second.next.next来实现
second.next = second.next.next;
// 返回新的链表的头节点(由于我们使用了dummy,所以实际返回的是dummy.next)
ListNode ans = dummy.next;
return ans;
}
}
82.【中等】删除排序链表中的重复元素 II
给定一个已排序的链表的头 head , 删除原始链表中所有重复数字的节点,只留下不同的数字 。返回 已排序的链表 。
示例 1:
- 输入:head = [1,2,3,3,4,4,5]
- 输出:[1,2,5]
示例 2:
- 输入:head = [1,1,1,2,3]
- 输出:[2,3]
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
// 定义一个方法,用于删除链表中重复的元素
public ListNode deleteDuplicates(ListNode head) {
// 如果链表为空,则直接返回
if (head == null) {
return head;
}
// 创建一个虚拟头节点dummy,其值为0,并指向原链表的头节点head
// 这样做是为了方便处理删除头节点的情况
ListNode dummy = new ListNode(0, head);
// cur指针初始化为虚拟头节点dummy,用于遍历链表
ListNode cur = dummy;
// 当cur的下一个节点和再下一个节点都不为空时,进行循环
while (cur.next != null && cur.next.next != null) {
// 如果cur的下一个节点和再下一个节点的值相等,说明有重复元素
if (cur.next.val == cur.next.next.val) {
// 记录当前重复元素的值
int x = cur.next.val;
// 删除所有值为x的节点,直到cur的下一个节点的值不再等于x
while (cur.next != null && cur.next.val == x) {
cur.next = cur.next.next; // 删除当前重复节点
}
} else {
// 如果没有重复元素,cur指针向后移动一位
cur = cur.next;
}
}
// 遍历结束后,返回处理后的链表的头节点(虚拟头节点的下一个节点)
return dummy.next;
}
}
✨ 这就是今天要分享给大家的全部内容了,我们下期再见!😊
🏠 我在CSDN等你哦!我的主页😍