142.环形链表II
题目
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
代码
class Solution {
public ListNode detectCycle(ListNode head) {
ListNode slow = head;
ListNode fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) {// 有环
ListNode index1 = fast;
ListNode index2 = head;
// 两个指针,从头结点和相遇结点,各走一步,直到相遇,相遇点即为环入口
while (index1 != index2) {
index1 = index1.next;
index2 = index2.next;
}
return index1;
}
}
return null;
}
}
21.合并两个有序链表
题目
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
代码
class Solution {
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
ListNode head = new ListNode();
ListNode pre = head;
ListNode p = list1;
ListNode q = list2;
while (p != null && q != null) {
if (p.val < q.val) {
pre.next = p;
p = p.next;
} else {
pre.next = q;
q = q.next;
}
pre = pre.next;
}
pre.next = p == null ? q : p;
return head.next;
}
}
2.两数之和
题目
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
代码
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
// 创建一个哨兵节点,方便处理边界情况
ListNode dummy = new ListNode(0);
ListNode current = dummy; // 当前指针,用于构建新链表
int carry = 0; // 进位值
// 遍历两个链表,直到所有节点都处理完毕
while (l1 != null || l2 != null) {
// 获取当前节点的值,如果链表已经遍历完,则取0
int x = (l1 != null) ? l1.val : 0;
int y = (l2 != null) ? l2.val : 0;
// 计算当前位的和以及新的进位值
int sum = carry + x + y;
carry = sum / 10; // 新的进位值
current.next = new ListNode(sum % 10); // 当前位的值
// 移动指针
current = current.next;
if (l1 != null) l1 = l1.next;
if (l2 != null) l2 = l2.next;
}
// 如果最后有进位,则需要再添加一个节点
if (carry > 0) {
current.next = new ListNode(carry);
}
// 返回结果链表的头节点(跳过哨兵节点)
return dummy.next;
}
}
19.删除链表的倒数第N个结点
题目
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
代码
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
//新建一个虚拟头节点指向head
ListNode dummyNode = new ListNode(0);
dummyNode.next = head;
//快慢指针指向虚拟头节点
ListNode fastIndex = dummyNode;
ListNode slowIndex = dummyNode;
// 只要快慢指针相差 n 个结点即可
for (int i = 0; i <= n; i++) {
fastIndex = fastIndex.next;
}
while (fastIndex != null) {
fastIndex = fastIndex.next;
slowIndex = slowIndex.next;
}
// 此时 slowIndex 的位置就是待删除元素的前一个位置。
// 具体情况可自己画一个链表长度为 3 的图来模拟代码来理解
// 检查 slowIndex.next 是否为 null,以避免空指针异常
if (slowIndex.next != null) {
slowIndex.next = slowIndex.next.next;
}
return dummyNode.next;
}
}
24.两两交换链表中的节点
题目
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
代码
class Solution {
public ListNode swapPairs(ListNode head) {
ListNode dumyhead = new ListNode(-1); // 设置一个虚拟头结点
dumyhead.next = head; // 将虚拟头结点指向head,这样方便后面做删除操作
ListNode cur = dumyhead;
ListNode temp; // 临时节点,保存两个节点后面的节点
ListNode firstnode; // 临时节点,保存两个节点之中的第一个节点
ListNode secondnode; // 临时节点,保存两个节点之中的第二个节点
while (cur.next != null && cur.next.next != null) {
temp = cur.next.next.next;
firstnode = cur.next;
secondnode = cur.next.next;
cur.next = secondnode; // 步骤一
secondnode.next = firstnode; // 步骤二
firstnode.next = temp; // 步骤三
cur = firstnode; // cur移动,准备下一轮交换
}
return dumyhead.next;
}
}
25.K个一组翻转链表
题目
给你链表的头节点 head ,每 k 个节点一组进行翻转,请你返回修改后的链表。
k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。
你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。
代码
class Solution {
public ListNode reverseKGroup(ListNode head, int k) {
ListNode h = new ListNode(0, head);
ListNode pre = h;
ListNode nextKHead = h.next;
while (nextKHead != null) {
int i = 0;
while (i < k) {
i++;
nextKHead = nextKHead.next;
if (nextKHead == null) break;
}
if (i < k) break;
ListNode t = pre.next;
reverseK(pre, nextKHead);
pre = t;
}
return h.next;
}
private void reverseK(ListNode pre, ListNode nextKHead) {
ListNode p = pre.next.next;
pre.next.next = nextKHead;
while (p != nextKHead) {
ListNode next = p.next;
p.next = pre.next;
pre.next = p;
p = next;
}
}
}
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 作为传入参数。
代码
class Solution {
public Node copyRandomList(Node head) {
Map<Node, Node> map = new HashMap<>();
Node p = head;
while (p != null) {
map.put(p, new Node(p.val));
p = p.next;
}
p = head;
while (p != null) {
map.get§.next = map.get(p.next);
map.get§.random = map.get(p.random);
p = p.next;
}
return map.get(head);
}
}
148.排序链表
题目
给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。
代码
class Solution {
public ListNode sortList(ListNode head) {
if (head == null || head.next == null) return head;
ListNode p = head;
ListNode q = head.next;
while (q != null && q.next != null) {
p = p.next;
q = q.next.next;
}
ListNode second = p.next;
p.next = null;
return merge(sortList(head), sortList(second));
}
public ListNode merge(ListNode l1, ListNode l2) {
ListNode head = new ListNode();
ListNode p = l1;
ListNode q = l2;
ListNode tail = head;
while (p != null && q != null) {
if (p.val < q.val) {
tail.next = p;
p = p.next;
} else {
tail.next = q;
q = q.next;
}
tail = tail.next;
}
tail.next = p == null ? q : p;
return head.next;
}
}
23.合并K个升序链表
题目
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
思路:递归调用merge函数,主要问题就是如何构造这个递归。下面这个代码使用的思路是首先判断lists的长度,长度为0表示空,直接返回空;长度为1直接返回第一个链表;长度为2返回合并后的链表;长度大于2则进行拆分,拆成两个长度平均的链表组递归调用自身。
代码
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
int n = lists.length;
if (n == 0) return null;
else if (n == 1) {
return lists[0];
} else if (n == 2) {
return merge(lists[0], lists[1]);
} else {
ListNode[] lists1 = new ListNode[(n + 1) / 2];
ListNode[] lists2 = new ListNode[n / 2];
for (int i = 0; i < n; i++) {
if (i < lists1.length) {
lists1[i] = lists[i];
} else {
lists2[i - lists1.length] = lists[i];
}
}
return merge(mergeKLists(lists1), mergeKLists(lists2));
}
}
public ListNode merge(ListNode l1, ListNode l2) {
ListNode head = new ListNode();
ListNode p = l1;
ListNode q = l2;
ListNode tail = head;
while (p != null && q != null) {
if (p.val < q.val) {
tail.next = p;
p = p.next;
} else {
tail.next = q;
q = q.next;
}
tail = tail.next;
}
tail.next = p == null ? q : p;
return head.next;
}
}
146.LRU缓存
题目
请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。
实现 LRUCache 类:
- LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存
- int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
- void put(int key, int value) 如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。
函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。
示例:
输入
"LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"
\[2\], \[1, 1\], \[2, 2\], \[1\], \[3, 3\], \[2\], \[4, 4\], \[1\], \[3\], \[4\]
输出
null, null, null, 1, null, -1, null, -1, 3, 4
解释
LRUCache lRUCache = new LRUCache(2);
lRUCache.put(1, 1); // 缓存是 {1=1}
lRUCache.put(2, 2); // 缓存是 {1=1, 2=2}
lRUCache.get(1); // 返回 1
lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}
lRUCache.get(2); // 返回 -1 (未找到)
lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}
lRUCache.get(1); // 返回 -1 (未找到)
lRUCache.get(3); // 返回 3
lRUCache.get(4); // 返回 4
思路:使用哈希表和双向链表实现
代码
class LRUCache {
int capacity;
int size;
DLinkNode head;
DLinkNode tail;
Map<Integer, DLinkNode> map;
public LRUCache(int capacity) {
this.capacity = capacity;
this.size = 0;
map = new HashMap<>();
head = new DLinkNode();
tail = new DLinkNode();
head.next = tail;
tail.pre = head;
}
public int get(int key) {
if (!map.containsKey(key)) return -1;
DLinkNode node = remove(key);
add(node);
return node.val;
}
public void put(int key, int value) {
if (map.containsKey(key)) {
remove(key);
size--;
}
if (size == capacity) {
remove(tail.pre.key);
size--;
}
add(new DLinkNode(key, value));
size++;
}
public DLinkNode remove(int key) {
DLinkNode node = map.get(key);
node.pre.next = node.next;
node.next.pre = node.pre;
map.remove(key);
return node;
}
public void add(DLinkNode node) {
node.next = head.next;
node.pre = head;
head.next.pre = node;
head.next = node;
map.put(node.key, node);
}
class DLinkNode {
int key;
int val;
DLinkNode pre;
DLinkNode next;
public DLinkNode() {
}
public DLinkNode(int key, int val) {
this.key = key;
this.val = val;
this.pre = null;
this.next = null;
}
}
}