160相交链表
题目:给你两个单链表的头节点headA和headB,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回null。
代码随想录的解法:
求出两个链表的长度,并求出两个链表长度的差值,然后让curA移动到,和curB 末尾对齐的位置。此时我们就可以比较curA和curB是否相同,如果不相同,同时向后移动curA和curB,如果遇到curA == curB,则找到交点。
java
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode curA=headA;
ListNode curB=headB;
int lenA=0;
int lenB=0;
while(curA!=null){
curA=curA.next;
lenA++;
}
while(curB!=null){
curB=curB.next;
lenB++;
}
curA=headA;
curB=headB;
if (lenB > lenA) { // 让curA为最长链表的头,lenA为其长度
//1. swap (lenA, lenB);
int tmpLen = lenA;
lenA = lenB;
lenB = tmpLen;
//2. swap (curA, curB);
ListNode tmpNode = curA;
curA = curB;
curB = tmpNode;
}
int gap=lenA-lenB;
while(gap>0){
curA=curA.next;
gap--;
}
while(curA!=null){
if(curA==curB){
return curA;
}
curA=curA.next;
curB=curB.next;
}
return null;
}
}
看了一个觉得很好的题解的方法:

java
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode p=headA;
ListNode q=headB;
while(p!=q){
if(p!=null){
p=p.next;
}else{
p=headB;
}
if(q!=null){
q=q.next;
}else{
q=headA;
}
}
return p;
}
}
206反转链表
题目:给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
双指针一前一后
java
class Solution {
public ListNode reverseList(ListNode head) {
ListNode cur=head;
ListNode pre=null;
ListNode temp=null;
while(cur!=null){
temp=cur.next;
cur.next=pre;
pre=cur;
cur=temp;
}
return pre;
}
}
234回文链表
题目:给你一个单链表的头节点head,请你判断该链表是否为回文链表。如果是,返回 true;否则,返回 false。
用数组复制一个单链表,再用双指针来判断。浪费空间,但是思路简单。
java
class Solution {
public boolean isPalindrome(ListNode head) {
List<Integer> vals=new ArrayList<>();
ListNode cur=head;
while(cur!=null){
vals.add(cur.val);
cur=cur.next;
}
int left=0;
int right=vals.size()-1;
while(left<right){
if(!vals.get(left).equals(vals.get(right))){
return false;
}
left++;
right--;
}
return true;
}
}
避免使用 O(n) 额外空间的方法就是改变输入。我们可以将链表的后半部分反转(修改链表结构),然后将前半部分和后半部分进行比较。比较完成后我们应该将链表恢复原样。虽然不需要恢复也能通过测试用例,但是使用该函数的人通常不希望链表结构被更改。
该方法虽然可以将空间复杂度降到 O(1),但是在并发环境下,该方法也有缺点。在并发环境下,函数运行时需要锁定其他线程或进程对链表的访问,因为在函数执行过程中链表会被修改。
整个流程可以分为以下五个步骤:找到前半部分链表的尾节点。反转后半部分链表。判断是否回文。恢复链表。返回结果。
执行步骤一,我们可以计算链表节点的数量,然后遍历链表找到前半部分的尾节点。
我们也可以使用快慢指针在一次遍历中找到:慢指针一次走一步,快指针一次走两步,快慢指针同时出发。当快指针移动到链表的末尾时,慢指针恰好到链表的中间。通过慢指针将链表分为两部分。若链表有奇数个节点,则中间的节点应该看作是前半部分。步骤二可以使用206反转链表问题中的解决方法来反转链表的后半部分。步骤三比较两个部分的值,当后半部分到达末尾则比较完成,可以忽略计数情况中的中间节点。步骤四与步骤二使用的函数相同,再反转一次恢复链表本身。
141环形链表
题目:给你一个链表的头节点 head ,判断链表中是否有环。如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。如果链表中存在环,则返回 true 。 否则,返回 false。
直接使用快慢指针判断。
java
public class Solution {
public boolean hasCycle(ListNode head) {
ListNode slow=head;
ListNode fast=head;
while(fast!=null && fast.next!=null){
fast=fast.next.next;
slow=slow.next;
if(fast==slow){
return true;
}
}
return false;
}
}
142 环形链表II
题目:给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。不允许修改链表。
和141比多一个返回环的入口。
找环的入口

根据这个图,慢指针走了x+y,快指针走了x + y + n (y + z)。相同的时间不同的速度可以得到一个等式 (x + y) * 2 = x + y + n (y + z),从而得到 x = n (y + z) -- y。x其实就是我们要求的环的入口。
接下来是重点:将这个等式变换一下:x = (n - 1) (y + z) + z (这里n一定大于等于1)
这个等式说明,从头结点出发一个指针,从快慢指针的相遇节点也出发一个指针,这两个指针每次只走一个节点,那么当这两个指针相遇的时候就是环形入口的节点。(在相遇节点处,定义一个指针index1,在头结点处定一个指针index2。让index1和index2同时移动,每次移动一个节点,那么他们相遇的地方就是环形入口的节点。)
java
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode fast=head;
ListNode slow=head;
while(fast!=null && fast.next!=null){
fast=fast.next.next;
slow=slow.next;
if(fast==slow){
ListNode index1=head;
ListNode index2=fast;
while(index1!=index2){
index1=index1.next;
index2=index2.next;
}
return index1;
}
}
return null;
}
}
21 合并两个有序链表
题目:将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
直接比,注意虚拟头节点。
java
class Solution {
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
ListNode dummy=new ListNode();
ListNode cur=dummy;
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;
}
if(list1!=null){
cur.next=list1;
}else{
cur.next=list2;
}
return dummy.next;
}
}
2 两数相加
题目:给你两个非空的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储一位数字。请你将两个数相加,并以相同形式返回一个表示和的链表。你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例1:输入:l1 = [2,4,3], l2 = [5,6,4],输出:[7,0,8],解释:342 + 465 = 807.
使用递归来一个一个处理加法。
java
/**
* 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) {
return add(l1,l2,0);
}
private ListNode add(ListNode l1,ListNode l2,int carry){
if(l1==null && l2==null && carry==0){
return null;
}
int s=carry;
if(l1!=null){
s=s+l1.val;
l1=l1.next;
}
if(l2!=null){
s=s+l2.val;
l2=l2.next;
}
return new ListNode(s%10, add(l1,l2,s/10));
}
}
19 删除链表的倒数第n个节点
题目:给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
快慢指针:
java
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy=new ListNode();
dummy.next=head;
ListNode fast=dummy;
ListNode slow=dummy;
for(int i=0;i<=n;i++){
fast=fast.next;
}
while(fast!=null){
fast=fast.next;
slow=slow.next;
}
slow.next=slow.next.next;
return dummy.next;
}
}
我最开始return的是head,但是如果链表只有1个节点且节点被删,这个就无法通过。
24 两两交换链表中的节点
题目:给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
示例1:输入:head = [1,2,3,4],输出:[2,1,4,3]
示例 2:输入:head = [],输出:[]
示例 3:输入:head = [1],输出:[1]
双指针,交换的时候记得画图。
java
class Solution {
public ListNode swapPairs(ListNode head) {
ListNode dummy=new ListNode();
dummy.next=head;
ListNode cur=dummy;
while(cur.next!=null && cur.next.next!=null){
ListNode node1=cur.next;
ListNode node2=cur.next.next;
cur.next=node2;
node1.next=node2.next;
node2.next=node1;
cur=cur.next.next;
}
return dummy.next;
}
}
25 K个一组翻转链表
题目:给你链表的头节点 head ,每 k 个节点一组进行翻转,请你返回修改后的链表。k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

java
class Solution {
public ListNode reverseKGroup(ListNode head, int k) {
ListNode dummy=new ListNode(0);
dummy.next=head;
ListNode pre = dummy;//创建一前一后两个指针,用来截取每一个k长度的链表进行分次反转
ListNode end = dummy;
while(end.next!=null){
for (int i = 0; i < k && end != null; i++) end = end.next;
if (end == null) break;
ListNode start = pre.next;
ListNode next = end.next;
end.next=null;
pre.next=reverse(start);
start.next = next;
pre = start;
end=pre;
}
return dummy.next;
}
private ListNode reverse(ListNode head){
ListNode last = null; //初始化一个尾结点
ListNode cur = head;
while (cur != null) {
ListNode temp = cur.next;
cur.next = last;
last = cur;
cur = temp;
}
return last;
}
}
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 作为传入参数。

java
class Solution {
public Node copyRandomList(Node head) {
for(Node cur=head;cur!=null;cur=cur.next.next){// 复制每个节点,把新节点直接插到原节点的后面
cur.next=new Node(cur.val,cur.next);
}
for(Node cur=head;cur!=null;cur=cur.next.next){ // 遍历交错链表中的原链表节点
if(cur.random!=null){
cur.next.random=cur.random.next; // 要复制的 random 是cur.random的复制节点,即下一个节点
}
}
Node dummy=new Node(0);
Node tail=dummy;
for(Node cur=head;cur!=null;cur=cur.next,tail=tail.next){ // 把交错链表分离成两个链表
Node copy=cur.next;
tail.next=copy; // 把新节点插在 tail 的后面,构建新的链表
cur.next=copy.next;
}
return dummy.next;
}
}
交错链表分离的时候记得画图,不然容易弄错。
148排序链表
题目:给你链表的头结点 head ,请将其按升序排列并返回排序后的链表。
归并排序:找到链表的中间结点的前一个节点,并断开中间节点与其前一个节点的连接。这样我们就把原链表均分成了两段更短的链表。分治,递归调用 sortList,分别排序 head(只有前一半)和mid。排序后,得到了两个有序链表,那么合并两个有序链表,得到排序后的链表,返回链表头节点。
java
class Solution {
public ListNode sortList(ListNode head) {
if(head==null || head.next==null) return head;
ListNode mid=middlenode(head);
head=sortList(head);
mid=sortList(mid);
return merge(head,mid);
}
private ListNode middlenode(ListNode head){ //找中间节点,并断开成两个
ListNode slow=head;
ListNode fast=head;
ListNode pre=head;
while(fast!=null && fast.next!=null){
pre=slow; //记录slow前一个节点
slow=slow.next;
fast=fast.next.next;
}
pre.next=null;
return slow;
}
private ListNode merge(ListNode list1,ListNode list2){
ListNode dummy=new ListNode();
ListNode cur=dummy;
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;
}
if(list1!=null){
cur.next=list1;
}else{
cur.next=list2;
}
return dummy.next;
}
}
链表的题经常需要将很多简单操作合到一个题目中。
23 合并K个升序链表
题目:给你一个链表数组,每个链表都已经按升序排列。请你将所有链表合并到一个升序链表中,返回合并后的链表。
示例 1:输入:lists = [[1,4,5],[1,3,4],[2,6]],输出:[1,1,2,3,4,4,5,6]
解释:链表数组如下:[1->4->5, 1->3->4, 2->6]
将它们合并到一个有序链表中得到。1->1->2->3->4->4->5->6
用最小堆实现。初始把所有链表的头节点入堆,然后不断弹出堆中最小节点 x,如果 x.next 不为空就加入堆中。循环直到堆为空。把弹出的节点按顺序拼接起来,就得到了答案。
java
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
PriorityQueue<ListNode> pq=new PriorityQueue<>((a,b)->a.val-b.val);
for(ListNode head:lists){ //所有非空链表的头节点入堆
if(head!=null){
pq.offer(head); //[1,1,2]
}
}
ListNode dummy=new ListNode();
ListNode cur=dummy;
while(!pq.isEmpty()){
ListNode node=pq.poll(); //[1,2]
if(node.next!=null){
pq.offer(node.next); //[1,2,4],然后在while中不断poll,offer
}
cur.next=node;
cur=cur.next;
}
return dummy.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

java
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); // 哨兵节点
private final 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); // getNode 会把对应节点移到链表头部
return node != null ? node.value : -1;
}
public void put(int key, int value) {
Node node = getNode(key); // getNode 会把对应节点移到链表头部
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;
}
}