一定要有模版思想,特别是反转链表,直接记住。
https://leetcode.cn/problems/intersection-of-two-linked-lists/
指针 A 先遍历完链表 headA ,再开始遍历链表 headB ,当走到 node 时,共走步数为:
a+(b−c)
指针 B 先遍历完链表 headB ,再开始遍历链表 headA ,当走到 node 时,共走步数为:
b+(a−c)
这种方法的时间复杂度是O(m + n),其中m和n分别是两个链表的长度,因为它最多遍历两个链表各一次。空间复杂度是O(1),因为它只使用了两个额外的指针。
// 定义一个名为Solution的类
public class Solution {
// 定义一个方法getIntersectionNode,接收两个链表的头节点headA和headB作为参数,返回它们的第一个相交节点
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
// 使用两个指针A和B分别指向链表headA和headB的头节点
ListNode A = headA, B = headB;
// 使用一个while循环来遍历链表,直到两个指针相等(找到相交节点或都为null)或遍历完两个链表
while (A != B) {
// 如果指针A不为null,则将其移动到下一个节点;否则,将其重新指向链表headB的头节点
A = A != null ? A.next : headB;
// 如果指针B不为null,则将其移动到下一个节点;否则,将其重新指向链表headA的头节点
B = B != null ? B.next : headA;
}
// 当两个指针相等时,返回它们指向的节点(可能是相交节点,也可能是null,表示没有相交节点)
return A;
}
}
https://leetcode.cn/problems/reverse-linked-list/
核心就是要背下来
/**
* 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 reverseList(ListNode head) {
ListNode pre = null;
ListNode cur = head;
while (cur != null) {
ListNode nxt = cur.next;
cur.next = pre;
pre = cur;
cur = nxt;
}
return pre;
}
}
https://leetcode.cn/problems/palindrome-linked-list/
找到中点,反转后半段,与前半段进行比较
class Solution {
// 判断链表是否是回文链表
public boolean isPalindrome(ListNode head) {
// 找到链表的中点
ListNode mid = mid(head);
// 反转链表从中点开始的后半部分
ListNode rev = revList(mid);
// 同时遍历原始链表的前半部分和反转后的后半部分,比较对应节点的值
while(rev != null){
if(head.val != rev.val) return false; // 如果对应节点的值不相等,则链表不是回文链表
rev = rev.next; // 移动反转链表中的指针
head = head.next; // 移动原始链表中的指针
}
return true; // 如果所有对应节点的值都相等,则链表是回文链表
}
// 使用快慢指针法找到链表的中点
ListNode mid(ListNode head){
ListNode slow = head; // 慢指针
ListNode fast = head; // 快指针
while(fast != null && fast.next != null){ // 快指针到达链表末尾或其前一个节点时停止
slow = slow.next; // 慢指针每次移动一步
fast = fast.next.next; // 快指针每次移动两步
}
return slow; // 当快指针到达链表末尾或其前一个节点时,慢指针指向链表的中点
}
// 反转链表
ListNode revList(ListNode head){
ListNode pre = null; // 前一个节点,初始化为null
ListNode cur = head; // 当前节点,从链表的头开始
while(cur != null){ // 遍历链表直到末尾
ListNode nxt = cur.next; // 保存当前节点的下一个节点
cur.next = pre; // 将当前节点的next指针指向前一个节点,实现反转
pre = cur; // 前一个节点更新为当前节点
cur = nxt; // 当前节点更新为下一个节点
}
return pre; // 当原链表的末尾节点成为反转链表的头节点时,返回该头节点
}
}
https://leetcode.cn/problems/linked-list-cycle/
快慢指针
public class Solution {
public boolean hasCycle(ListNode head) {
ListNode slow = head, fast = head; // 乌龟和兔子同时从起点出发
while (fast != null && fast.next != null) {
slow = slow.next; // 乌龟走一步
fast = fast.next.next; // 兔子走两步
if (fast == slow) // 兔子追上乌龟(套圈),说明有环
return true;
}
return false; // 访问到了链表末尾,无环
}
}
https://leetcode.cn/problems/linked-list-cycle-ii/
图片来源:零茶山艾府
public 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 cur = head;
//再head与slow指针走,找相遇点为所求
while (cur != slow) {
cur = cur.next;
slow = slow.next;
}
return cur;
}
}
return null;
}
}
https://leetcode.cn/problems/merge-two-sorted-lists/
双指针排序
class Solution {
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
// 创建一个哑节点(dummy node),用于简化边界情况的处理
ListNode ans = new ListNode();
// cur用于遍历新链表,初始指向哑节点
ListNode cur = ans;
// 当两个链表都不为空时,进行合并
while(list1!=null && list2!=null){
// 如果list1的当前节点值小于list2的当前节点值
if(list1.val<list2.val){
// 将cur的next指向list1的当前节点
cur.next = list1;
// list1前进到下一个节点
list1 = list1.next;
}else{
// 否则,将cur的next指向list2的当前节点
cur.next = list2;
// list2前进到下一个节点
list2 = list2.next;
}
// cur前进到下一个节点,为下一次比较做准备
cur = cur.next;
}
// 当其中一个链表为空时,直接将另一个链表的剩余部分连接到结果链表的末尾
cur.next = list1 != null ? list1 : list2;
// 返回合并后的链表(跳过哑节点)
return ans.next;
}
}
https://leetcode.cn/problems/add-two-numbers/
easy,处理一下余数就行
/**
* 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 addTwoNumbers(ListNode l1, ListNode l2) {
ListNode ans = new ListNode();
ListNode cur = ans;
int ex = 0;
while(l1!=null||l2!=null){
int v = 0;
if(l1 != null) v+=l1.val;
if(l2 != null) v+=l2.val;
v+=ex;
ex = v/10;
v%=10;
cur.next = new ListNode(v);
cur = cur.next;
if(l1 != null)
l1 = l1.next;
if(l2 != null)
l2 = l2.next;
}
if(ex == 1) {
cur.next = new ListNode(ex);
}
return ans.next;
}
}
https://leetcode.cn/problems/remove-nth-node-from-end-of-list/
关键在于怎么找到第N个节点,先让A从头节点走n步,然后B从头结点与A同时走,A走到尾部,B刚好到倒数第n个节点
/**
* 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 removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(0,head);
ListNode ll =dummy;
ListNode rr = dummy;
while(n-->0){
rr = rr.next;
}
while(rr.next!=null){
ll = ll.next;
rr = rr.next;
}
ll.next = ll.next.next;
return dummy.next;
}
}
https://leetcode.cn/problems/swap-nodes-in-pairs/
基本操作,链表节点的添加与删除,因为要操作头结点,增加哑节点
/**
* 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 swapPairs(ListNode head) {
ListNode ans = new ListNode(0, head);
ListNode h0 = ans;
ListNode h1 = ans.next;
while (h1!=null && h1.next!= null) {
ListNode h2 = h1.next;
ListNode h3 = h2.next;
h0.next = h2;
h2.next = h1;
h1.next = h3;
h0 = h1;
h1 = h3;
}
return ans.next;
}
}
https://leetcode.cn/problems/reverse-nodes-in-k-group/
用head计数,ans做答案,反转后保存在pre,再放到ans。
就是一个反转链表,背下模版即可。
/**
* 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 reverseKGroup(ListNode head, int k) {
ListNode ans = new ListNode(0,head);
ListNode p0 = ans;
ListNode pre = null;
ListNode cur = p0.next;
int cnt = 0;
while(head!=null){
head = head.next;
cnt++;
if(cnt % k == 0){
for(int i = 0;i<k;i++){
ListNode nxt= cur.next;
cur.next = pre;
pre = cur;
cur = nxt;
}
p0.next.next = cur;
p0.next = pre;
for(int i = 0;i<k;i++) p0= p0.next;
}
}
return ans.next;
}
}
https://leetcode.cn/problems/copy-list-with-random-pointer/
就是深拷贝复制链表。没什么算法,就是个窍门,看注释能看懂。
/*
// 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;
// 第一步:在原链表的每个节点后面插入一个新节点
// 新节点的值与当前节点相同,新节点的 next 指向当前节点的 next
for(Node cur = head; cur != null; cur = cur.next.next) {
cur.next = new Node(cur.val, cur.next);
}
// 第二步:设置新节点的 random 指针
// 遍历链表,将原节点 random 指针指向的节点的 next(即对应的新节点)赋值给新节点的 random
for(Node cur = head; cur != null; cur = cur.next.next) {
if(cur.random != null) {
cur.next.random = cur.random.next;
}
}
// 第三步:拆分链表
// 将原链表和新链表分离
// `ans` 是新链表的头节点,`cur` 用于遍历原链表,`idx` 用于遍历新链表
Node ans = head.next;
Node cur = head;
Node idx = ans;
// 遍历链表,将原链表和新链表拆分
// 同时重新连接原链表中的 next 指针和新链表中的 next 指针
while(cur.next.next != null) {
// 恢复原链表的 next 指针
cur.next = cur.next.next;
// 设置新链表的 next 指针
idx.next = cur.next.next;
// 移动到下一个节点
cur = cur.next;
idx = idx.next;
}
// 最后一个节点的 next 指针需要手动设置为 null,避免与原链表产生连接
cur.next = null;
// 返回新链表的头节点
return ans;
}
}
https://leetcode.cn/problems/sort-list/
就是归并排序的思路一毛一样
/**
* 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 sortList(ListNode head) {
// 如果链表为空或只有一个节点,则无需排序,直接返回
if (head == null || head.next == null) {
return head;
}
// 使用快慢指针找到链表的中点,并将链表从中点断开,得到两个子链表
ListNode head2 = mid(head);
// 递归地对两个子链表进行排序
head = sortList(head);
head2 = sortList(head2);
// 将排序后的两个子链表合并成一个有序链表
return merge(head, head2);
}
// 将两个链表从中间断开的辅助函数
// 使用快慢指针技巧找到链表的中点,并断开链表,返回后半部分链表的头节点
ListNode mid(ListNode head) {
ListNode slow = head; // 慢指针,每次移动一步
ListNode fast = head; // 快指针,每次移动两步
ListNode pre = head; // 用于记录慢指针的前一个节点,以便最后断开链表
while (fast != null && fast.next != null) { // 当快指针未到达链表尾部时继续循环
pre = slow; // 更新pre为当前慢指针的位置
slow = slow.next; // 慢指针前进一步
fast = fast.next.next; // 快指针前进两步
}
pre.next = null; // 断开链表,将前半部分和后半部分分开
return slow; // 返回后半部分链表的头节点
}
// 合并两个有序链表的辅助函数
ListNode merge(ListNode list1, ListNode list2) {
ListNode dummy = new ListNode(); // 创建一个哨兵节点,用于简化链表操作,dummy.next将指向合并后的新链表的头节点
ListNode cur = dummy; // cur用于遍历新链表,初始指向哨兵节点
while (list1 != null && list2 != null) { // 当两个链表都未遍历完时继续循环
if (list1.val < list2.val) { // 比较两个链表当前节点的值,选择较小的节点加入新链表
cur.next = list1;
list1 = list1.next;
} else {
cur.next = list2;
list2 = list2.next;
}
cur = cur.next; // cur移动到新链表的下一个位置
}
cur.next = list1 != null ? list1 : list2; // 将剩余未遍历完的链表直接接到新链表的末尾
return dummy.next; // 返回合并后的新链表的头节点(跳过哨兵节点)
}
}
https://leetcode.cn/problems/merge-k-sorted-lists/
归并,与上一题思路相同
/**
* 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 mergeKLists(ListNode[] lists) {
return mergeKLists(lists, 0, lists.length);
}
private ListNode mergeKLists(ListNode[] lists, int i, int j) {
int m = j - i;
if (m == 0) {
return null; // 注意输入的 lists 可能是空的
}
if (m == 1) {
return lists[i]; // 无需合并,直接返回
}
ListNode left = mergeKLists(lists, i, i + m / 2); // 合并左半部分
ListNode right = mergeKLists(lists, i + m / 2, j); // 合并右半部分
return mergeTwoLists(left, right); // 最后把左半和右半合并
}
// 21. 合并两个有序链表
private ListNode mergeTwoLists(ListNode list1, ListNode list2) {
ListNode dummy = new ListNode(); // 用哨兵节点简化代码逻辑
ListNode cur = dummy; // cur 指向新链表的末尾
while (list1 != null && list2 != null) {
if (list1.val < list2.val) {
cur.next = list1; // 把 list1 加到新链表中
list1 = list1.next;
} else { // 注:相等的情况加哪个节点都是可以的
cur.next = list2; // 把 list2 加到新链表中
list2 = list2.next;
}
cur = cur.next;
}
cur.next = list1 != null ? list1 : list2; // 拼接剩余链表
return dummy.next;
}
}
数据结构:
1.双向链表,链表头部有固定哑结点,pre指向链表尾部;
2.Map:存储key和key对应的Node;
class LRUCache {
private static class Node{
int key,value;
Node prev,next;
Node(int k,int v){
key = k;
value = v;
}
}
private final int capacity;
private final Node dummy = new Node(0, 0); // 哨兵节点
Map<Integer,Node> keyToNode = new HashMap<>();
public LRUCache(int capacity) {
this.capacity = capacity;
dummy.prev = dummy;
dummy.next = dummy;
}
public int get(int key) {
Node node = getNode(key);
return node!=null ? node.value : -1;
}
public void put(int key, int value) {
Node node = getNode(key);
if (node != null) { // 有这本书
node.value = value; // 更新 value
return;
}
node = new Node(key,value);
keyToNode.put(key,node);
pushFront(node);
if(keyToNode.size()>capacity){
Node backNode = dummy.prev;
keyToNode.remove(backNode.key);
remove(backNode); // 去掉最后一本书
}
}
// 获取 key 对应的节点,同时把该节点移到链表头部
private Node getNode(int key) {
if (!keyToNode.containsKey(key)) { // 没有这本书
return null;
}
Node node = keyToNode.get(key); // 有这本书
remove(node); // 把这本书抽出来
pushFront(node); // 放在最上面
return node;
}
// 删除一个节点(抽出一本书)
private void remove(Node x) {
x.prev.next = x.next;
x.next.prev = x.prev;
}
// 在链表头添加一个节点(把一本书放在最上面)
private void pushFront(Node x) {
x.prev = dummy;
x.next = dummy.next;
x.prev.next = x;
x.next.prev = x;
}
}
/**
* Your LRUCache object will be instantiated and called as such:
* LRUCache obj = new LRUCache(capacity);
* int param_1 = obj.get(key);
* obj.put(key,value);
*/