目录
对于链表问题,返回结果为头结点时,通常需要先初始化一个预先指针 pre,该指针的下一个节点指向真正的头结点head。使用预先指针的目的在于链表初始化时无可用节点值,而且链表构造过程需要指针移动,进而会导致头指针丢失,无法返回结果。
剑指Offer18.删除链表的节点
给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。
返回删除后的链表的头节点。
- pre指针为要删除节点的前一个值
- cur指针为要删除的值
class Solution {
public ListNode deleteNode(ListNode head, int val) {
ListNode dump=new ListNode(0);
dump.next =head;
ListNode pre = dump;
ListNode cur = head;
while(cur.val != val){
pre = cur;
cur=cur.next;
}
pre.next=cur.next;
return dump.next;
}
}
剑指Offer22.链表中倒数第k个节点
输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。
例如,一个链表有 6 个节点,从头节点开始,它们的值依次是 1、2、3、4、5、6。这个链表的倒数第 3 个节点是值为 4 的节点。
双指针
- 初始化:双指针都指向头节点 head
- 前指针 fast先向前走 k 步
- 双指针共同移动:每轮都向前走一步,直到fast走过链表尾部节点
class Solution {
public ListNode getKthFromEnd(ListNode head, int k) {
ListNode slow =head, fast = head;
for(int i=0; i < k; i++){
fast=fast.next;
}
while(fast != null){
slow=slow.next;
fast=fast.next;
}
return slow;
}
}
删除链表的倒数第N个节点
栈
我们弹出栈的第 n 个节点就是需要删除的节点,并且目前栈顶的节点就是待删除节点的前驱节点。
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(0, head);
Deque<ListNode> stack = new LinkedList<ListNode>();
ListNode cur = dummy;
while (cur != null) {
stack.push(cur);
cur = cur.next;
}
for (int i = 0; i < n; ++i) {
stack.pop();
}
ListNode prev = stack.peek();
prev.next = prev.next.next;
ListNode ans = dummy.next;
return ans;
}
}
双指针
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(0, head);
ListNode first = head;
ListNode second = dummy;
for (int i = 0; i < n; ++i) {
first = first.next;
}
while (first != null) {
first = first.next;
second = second.next;
}
second.next = second.next.next;
ListNode ans = dummy.next;
return ans;
}
}
剑指Offer06.从尾到头打印链表(栈)
输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。
class Solution {
public int[] reversePrint(ListNode head) {
Deque<ListNode> stack = new LinkedList<>();
ListNode temp =head;
while(temp != null){
stack.push(temp);
temp=temp.next;
}
int size =stack.size();
int[] res = new int[size];
for(int i=0; i<size; i++){
res[i]=stack.pop().val;
}
return res;
}
}
2.两数相加
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
- 将两个链表看成是相同长度的进行遍历,如果一个链表较短则在前面补 0,比如 987 + 23 = 987 + 023 = 1010
- 答案链表处相应位置的数字为 (n1+n2+carry) mod 10
- 进位为 (n1+n2+carry)/10
- 如果两个链表全部遍历完毕后,进位值为 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 {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode pre = new ListNode(0);
ListNode cur =pre;
int carry =0;
while(l1 != null || l2 != null){
int n1 =l1==null ?0 : l1.val;//如果一个链表较短则在前面补 0
int n2 =l2==null ?0 : l2.val;
int sum =n1+n2+carry;
carry = sum/10;
sum =sum %10;
cur.next=new ListNode(sum);
cur =cur.next;
if(l1!=null){
l1=l1.next;
}
if(l2!=null){
l2=l2.next;
}
}
if (carry==1){
cur.next=new ListNode(carry);
}
return pre.next;
}
}
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode head = null, tail = null;
int carry = 0;
while (l1 != null || l2 != null) {
int n1 = l1 != null ? l1.val : 0;
int n2 = l2 != null ? l2.val : 0;
int sum = n1 + n2 + carry;
if (head == null) {
head = tail = new ListNode(sum % 10);
} else {
tail.next = new ListNode(sum % 10);
tail = tail.next;
}
carry = sum / 10;
if (l1 != null) {
l1 = l1.next;
}
if (l2 != null) {
l2 = l2.next;
}
}
if (carry > 0) {
tail.next = new ListNode(carry);
}
return head;
}
}
合并两个有序链表
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode dump = new ListNode(0);
ListNode cur = dump;
while (l1 != null && l2 != null) {
if (l1.val < l2.val) {
cur.next = l1;
l1 = l1.next;
} else {
cur.next = l2;
l2 = l2.next;
}
cur = cur.next;
}
//然后把那个不为空的链表挂到新的链表中
cur.next = l1 == null ? l2 : l1;
return dump.next;
}
}
//递归
public class Solution {
public ListNode Merge(ListNode list1,ListNode list2) {
if(list1 == null){
return list2;
}
if(list2 == null){
return list1;
}
if(list1.val <= list2.val){
list1.next = Merge(list1.next, list2);
return list1;
}else{
list2.next = Merge(list1, list2.next);
return list2;
}
}
}
合并K个升序链表
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
/**
* 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) {
ListNode ans =null;
for(int i=0; i < lists.length; i++){
ans=merge(ans,lists[i]);
}
return ans;
}
public ListNode merge(ListNode a, ListNode b){
if(a==null || b == null){
return a==null? b:a;
}
ListNode dump =new ListNode(0);
ListNode cur =dump;
while(a!=null && b!= null){
if(a.val < b.val){
cur.next=a;
a=a.next;
}else{
cur.next=b;
b=b.next;
}
cur=cur.next;
}
cur.next=(a==null? b: a);
return dump.next;
}
}
反转链表
class Solution {
public ListNode reverseList(ListNode head) {
ListNode pre =null;
ListNode cur =head;
while(cur != null){
ListNode next = cur.next;
cur.next=pre;
pre=cur;//两个指针后移
cur=next;
}
//此时,pre指向头节点
return pre;
}
}
回文链表
//1、复制链表值到数组列表中。
//2、使用双指针法判断是否为回文。
class Solution {
public boolean isPalindrome(ListNode head) {
List<Integer> list = new ArrayList<>();
ListNode cur =head;
while(cur != null){
list.add(cur.val);
cur=cur.next;
}
int left=0;
int right=list.size()-1;
while(left < right){
if(!list.get(left).equals(list.get(right))){
return false;
}
left++;
right--;
}
return true;
}
}
方法er:快慢指针
- 找到前半部分链表的尾节点:慢指针一次走一步,快指针一次走两步,快慢指针同时出发。当快指针移动到链表的末尾时,慢指针恰好到链表的中间。通过慢指针将链表分为两部分。若链表有奇数个节点,则中间的节点应该看作是前半部分。
- 反转后半部分链表。
- 判断是否回文:当后半部分到达末尾则比较完成,可以忽略计数情况中的中间节点。
- 恢复链表:再反转一次恢复链表本身。
- 返回结果。
public boolean isPalindrome(ListNode head) {
if (head == null) {
return true;
}
ListNode fisrtEnd = endOfFirstHalf(head);
ListNode secodHead = reverse(fisrtEnd.next);
ListNode p1 = head;
ListNode p2 = secodHead;
while (p2 != null) {
if (p1.val == p2.val) {
p1 = p1.next;
p2 = p2.next;
} else {
return false;
}
}
// 还原链表并返回结果
fisrtEnd.next = reverse(secodHead);
return true;
}
public ListNode reverse(ListNode head) {
ListNode pre = null;
ListNode cur = head;
while (cur != null) {
ListNode next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
return pre;
}
public ListNode endOfFirstHalf(ListNode head) {
ListNode slow = head;
ListNode fast = head;
while (fast.next != null && fast.next.next != null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
环形链表
//快慢指针
//定义两个指针,一快一满。慢指针每次只移动一步,而快指针每次移动
//两步。初始时,慢指针在位置 head,而快指针在位置 head.next。这
//样一来,如果在移动的过程中,快指针反过来追上慢指针,就说明该链
//表为环形链表。否则快指针将到达链表尾部,该链表不为环形链表。
public class Solution {
public boolean hasCycle(ListNode head) {
if(head == null || head.next == null){
return false;
}
ListNode slow = head;
ListNode fast = head.next;
while(slow != fast){
if(fast == null || fast.next == null){
return false;
}
slow=slow.next;
fast=fast.next.next;
}
return true;
}
}
剑指Offer52.两个链表的第一个公共节点
输入两个链表,找出它们的第一个公共节点。
两个链表长度分别为L1+C、L2+C, C为公共部分的长度。
- 第一个人走了L1+C步后,回到第二个人起点走L2步;
- 第2个人走了L2+C步后,回到第一个人起点走L1步;
- 当两个人走的步数都为L1+L2+C时这两个家伙就相爱了
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode A = headA;
ListNode B = headB;
while (A != B) {
// if (A != null) {
// A = A.next;
// } else {
// A = headB;
// }
// if (B != null) {
// B = B.next;
// } else {
// B = headA;
// }
A = A != null ? A.next : headB;
B = B != null ? B.next : headA;
}
return A;
}
}
876.链表的中间结点
给定一个头结点为 head 的非空单链表,返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点。
获取中间元素的问题。设有两个指针 fast 和 slow,初始时指向头节点。每次移动时,fast向后走两次,slow向后走一次,直到 fast 无法向后走两次。这使得在每轮移动之后。fast 和 slow 的距离就会增加一。设链表有 n 个元素,那么最多移动 n/2 轮。当 n 为奇数时,slow 恰好指向中间结点,当 n 为 偶数时,slow 恰好指向中间两个结点的靠前一个。
class Solution {
public ListNode middleNode(ListNode head) {
ListNode slow =head;
ListNode fast =head;
while(fast != null && fast.next != null){
slow=slow.next;
fast=fast.next.next;
}
return slow;
}
}
142.环形链表II
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
哈希表
我们遍历链表中的每个节点,并将它记录下来;一旦遇到了此前遍历过的节点,就可以判定链表中存在环。
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode pos = head;
Set<ListNode> visited = new HashSet<ListNode>();
while (pos != null) {
if (visited.contains(pos)) {
return pos;
} else {
visited.add(pos);
}
pos = pos.next;
}
return null;
}
}
从相遇点到入环点的距离加上 n-1 圈的环长,恰好等于从链表头部到入环点的距离。
1、fast = slow ,第一次相遇
- 设链表共有 a+b个节点,其中 链表头部到链表入口 有 a 个节点(不计链表入口节点), 链表环有 b个节点
- fast 走的步数是slow步数的 2倍,即 f = 2s
- fast 比 slow多走了 n 个环的长度,即 f = s + nb( 双指针都走过 a 步,然后在环内绕圈直到重合,重合时 fast 比 slow 多走 环的长度整数倍)
- 以上两式相减得:s = nb,f=2nb,即fast和slow 指针分别走了 2n,n 个 环的周长
- 所有 走到链表入口节点时的步数 是:k=a+nb(先走 a 步到入口节点,之后每绕 1 圈环( b步)都会再次到入口节点)。
- slow 指针走过的步数为 nb步。因此,我们只要想办法让 slow 再走 a 步停下来,就可以到环的入口。
2、第二次相遇
- slow指针 位置不变 ,将fast指针重新 指向链表头部节点 ;slow和fast同时每轮向前走 1 步,此时 f = 0,s = nb
- 当 fast 指针走到f = a 步时,slow 指针走到步s = a+nb,此时 两指针重合,并同时指向链表环入口 。
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode fast = head, slow = head;
while (true) {
if (fast == null || fast.next == null) return null;
fast = fast.next.next;
slow = slow.next;
if (fast == slow) break;
}
fast = head;
while (slow != fast) {
slow = slow.next;
fast = fast.next;
}
return fast;
}
}
143.重排链表
给定一个单链表 L 的头节点 head ,单链表 L 表示为:
L0 → L1 → … → Ln-1 → Ln
请将其重新排列后变为:
L0 → Ln → L1 → Ln-1 → L2 → Ln-2 → …
不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换
输入: head = [1,2,3,4]
输出: [1,4,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; }
* }
*/
public void reorderList(ListNode head) {
if (head == null || head.next == null || head.next.next == null) {
return;
}
ListNode slow = head;
ListNode fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
ListNode newHead = slow.next;
newHead = reverse(newHead);
slow.next = null;
ListNode p1 = head;
ListNode p2 = newHead;
ListNode temp1 = null;
ListNode temp2 = null;
while (p1 != null && p2 != null) {
temp1 = p1.next;
temp2 = p2.next;
p1.next = p2;
p1 = temp1;
p2.next = p1;
p2 = temp2;
}
}
public ListNode reverse(ListNode head) {
ListNode pre = null;
ListNode cur = head;
while (cur != null) {
ListNode next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
return pre;
}
LRU
package com.example.test;
import java.util.HashMap;
import java.util.Map;
/**
* LRUCache(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存
* int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
* void put(int key, int value) 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字-值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。
*
* @param
* @param
* @return
*/
public class LRUCache {
private Map<Integer, DLinkedNode> cache = new HashMap<>();
private int size;
private int capacity;
private DLinkedNode head, tail;
//初始化 LRU 缓存
public LRUCache(int capacity) {
this.size = 0;
this.capacity = capacity;
//使用伪头部和伪尾部节点
head = new DLinkedNode();
tail = new DLinkedNode();
head.next = tail;
tail.pre = head;
}
public int get(int key) {
DLinkedNode node = cache.get(key);
if (node == null) {
return -1;
}
moveToHead(node);
return node.value;
}
public void put(int key, int value) {
DLinkedNode node = cache.get(key);
if (node == null) {
DLinkedNode newNode = new DLinkedNode(key, value);
addHead(newNode);
cache.put(key, newNode);
size++;
if (size > capacity) {
DLinkedNode tail = removeTail();
cache.remove(tail.key);
size--;
}
} else {
node.value = value;
moveToHead(node);
}
}
//链表头部添加节点
public void addHead(DLinkedNode node) {
node.pre = head;
node.next = head.next;//head.next=tail;
head.next.pre = node;//尾节点的pre指针指向待添加节点
head.next = node;//
}
private void removeNode(DLinkedNode node) {
node.pre.next = node.next;
node.next.pre = node.pre;
}
public void moveToHead(DLinkedNode node) {
removeNode(node);
addHead(node);
}
public DLinkedNode removeTail() {
DLinkedNode res = tail.pre;
removeNode(res);
return res;
}
//创建一个双向链表
class DLinkedNode {
int key;
int value;
DLinkedNode pre;
DLinkedNode next;
public DLinkedNode() {
}
public DLinkedNode(int key, int value) {
this.key = key;
this.value = value;
}
}
}
25.K个一组翻转链表
给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。
k 是一个正整数,它的值小于或等于链表的长度。
如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。
public static ListNode reverseKGroup(ListNode head, int k) {
if (head == null || head.next == null){
return head;
}
//定义一个假的节点。
ListNode dummy=new ListNode(0);
//假节点的next指向head。
// dummy->1->2->3->4->5
dummy.next=head;
//初始化pre和end都指向dummy。pre指每次要翻转的链表的头结点的上一个节点。end指每次要翻转的链表的尾节点
ListNode pre=dummy;
ListNode end=dummy;
while(end.next!=null){
//循环k次,找到需要翻转的链表的结尾,这里每次循环要判断end是否等于空,因为如果为空,end.next会报空指针异常。
//dummy->1->2->3->4->5 若k为2,循环2次,end指向2
for(int i=0;i<k&&end != null;i++){
end=end.next;
}
//如果end==null,即需要翻转的链表的节点数小于k,不执行翻转。
if(end==null){
break;
}
//先记录下end.next,方便后面链接链表
ListNode next=end.next;
//然后断开链表
end.next=null;
//记录下要翻转链表的头节点
ListNode start=pre.next;
//翻转链表,pre.next指向翻转后的链表。1->2 变成2->1。 dummy->2->1
pre.next=reverse(start);
//翻转后头节点变到最后。通过.next把断开的链表重新链接。
start.next=next;
//将pre换成下次要翻转的链表的头结点的上一个节点。即start
pre=start;
//翻转结束,将end置为下次要翻转的链表的头结点的上一个节点。即start
end=start;
}
return dummy.next;
}
//链表翻转
// 例子: head: 1->2->3->4
public static ListNode reverse(ListNode head) {
//单链表为空或只有一个节点,直接返回原单链表
if (head == null || head.next == null){
return head;
}
//前一个节点指针
ListNode preNode = null;
//当前节点指针
ListNode curNode = head;
//下一个节点指针
ListNode nextNode = null;
while (curNode != null){
nextNode = curNode.next;//nextNode 指向下一个节点,保存当前节点后面的链表。
curNode.next=preNode;//将当前节点next域指向前一个节点 null<-1<-2<-3<-4
preNode = curNode;//preNode 指针向后移动。preNode指向当前节点。
curNode = nextNode;//curNode指针向后移动。下一个节点变成当前节点
}
return preNode;
}