链表
链表
移除链表元素【LC 203题】
给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。
方法一:注意可能前几个节点的值都是val,因此要用while循环删除头结点
// 不使用虚拟头结点
public ListNode removeElements(ListNode head, int val) {
while (head!=null && head.val==val) head=head.next;
ListNode cur=head;
while (cur!=null){
// 除了处理头结点要使用while循环,处理后续节点也要使用while循环,来去除连续的val节点
while (cur.next!=null && cur.next.val==val) cur.next=cur.next.next;
cur=cur.next;
}
return head;
}方法二:使用dummy虚拟头结点
// 使用虚拟头结点
public ListNode removeElements2(ListNode head, int val) {
ListNode dummy=new ListNode(0);
dummy.next=head;
ListNode pre=dummy;
ListNode cur=head;
while (cur!=null){
//如果当前元素的值等于val,说明需要移除,pre节点不用发生变化,还是原来的pre节点
if (cur.val==val) pre.next=cur.next;
else pre=cur;
cur=cur.next;
}
return dummy.next;
}设计链表【LC 707题】
你可以选择使用单链表或者双链表,设计并实现自己的链表。
单链表中的节点应该具备两个属性:val 和 next 。val 是当前节点的值,next 是指向下一个节点的指针/引用。
如果是双向链表,则还需要属性 prev 以指示链表中的上一个节点。假设链表中的所有节点下标从 0 开始。
实现 MyLinkedList 类:
MyLinkedList()初始化MyLinkedList对象。int get(int index)获取链表中下标为index的节点的值。如果下标无效,则返回1。void addAtHead(int val)将一个值为val的节点插入到链表中第一个元素之前。在插入完成后,新节点会成为链表的第一个节点。void addAtTail(int val)将一个值为val的节点追加到链表中作为链表的最后一个元素。void addAtIndex(int index, int val)将一个值为val的节点插入到链表中下标为index的节点之前。如果index等于链表的长度,那么该节点会被追加到链表的末尾。如果index比长度更大,该节点将 不会插入 到链表中。void deleteAtIndex(int index)如果下标有效,则删除链表中下标为index的节点。
package LinkedList;
public class MyLinkedList {
int size;
ListNode dummy; //虚拟头结点
public MyLinkedList() {
size=0;
dummy=new ListNode(0);
}
public int get(int index) {
if (index<0 || index>=size) return -1;
ListNode temp=dummy;
while(index-->=0){
temp=temp.next;
}
return temp.val;
}
public void addAtHead(int val) {
addAtIndex(0,val);
}
public void addAtTail(int val) {
addAtIndex(size,val);
}
public void addAtIndex(int index, int val) {
if (index<0 || index>size) return;
ListNode pre=dummy;
while (index>0){
pre=pre.next;
index--;
}
ListNode toAdd=new ListNode(val);
toAdd.next=pre.next;
pre.next=toAdd;
size++;
}
public void deleteAtIndex(int index) {
if (index<0 || index>=size) return;
size--;
ListNode pre=dummy;
while(index>0){
pre=pre.next;
index--;
}
pre.next=pre.next.next;
}
}反转链表【LC 206题】
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]双指针写法
public ListNode reverseList(ListNode head) {
ListNode cur=head,pre=null,nextNode=cur.next;
while (cur!=null){
nextNode=cur.next;
cur.next=pre;
pre=cur;
cur=nextNode;
}
return pre;
}递归写法
//递归写法
public ListNode reverseList2(ListNode head) {
return reverse(head,null);
}
public ListNode reverse(ListNode cur,ListNode pre){
if (cur==null) return pre;
ListNode next=cur.next;
cur.next=pre;
return reverse(next,cur);
}k个一组反转链表【LC 25题】
给你链表的头节点 head ,每 k 个节点一组进行翻转,请你返回修改后的链表。
k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。
你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。
package LinkedList;
public class ReverseKGroup {
public ListNode reverseKGroup(ListNode head, int k) {
if (head==null || head.next==null) return head;
ListNode tail=head;
for (int i = 0; i < k; i++) {
if (tail==null) return head;
tail=tail.next;
}
ListNode newHead=reverseList(head,tail);
head.next=reverseKGroup(tail,k);
return newHead;
}
private ListNode reverseList(ListNode head, ListNode tail) {
ListNode pre=null,next,cur=head;
while (cur!=tail){
next=cur.next;
cur.next=pre;
pre=cur;
cur=next;
}
return pre;
}
}两数相加【LC 2题】
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode dummy=new ListNode(0);
ListNode temp=dummy;
int carry=0; //表示进位
while(l1!=null || l2!=null ||carry!=0){
int x1=l1==null?0:l1.val;
int x2=l2==null?0:l2.val;
int sum=x1+x2+carry;
int current=sum%10;
carry=sum/10;
temp.next=new ListNode(current);
temp=temp.next;
// 这里也要注意 要做非空判断 不然直接往后走的话 会出现空指针异常
if(l1!=null) l1=l1.next;
if(l2!=null) l2=l2.next;
}
return dummy.next;
}
}LRU缓存【LC 146题】
请你设计并实现一个满足 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) 的平均时间复杂度运行。
class LRUCache {
// 用map和双向链表来实现LRU缓存
Node head;
Node tail;
HashMap<Integer,Node> map=new HashMap<>();
int capacity;
public LRUCache(int capacity) {
this.capacity=capacity;
head=new Node(-1,-1);
tail=new Node(-1,-1);
head.next=tail;
tail.pre=head;
}
public int get(int key) {
// 把访问过的放在链表最开头
if(map.containsKey(key)){
Node node=map.get(key);
cutNode(node);
insertHead(node);
return node.val;
}
return -1;
}
public void put(int key, int value) {
if(map.containsKey(key)){
Node node=map.get(key);
node.val=value;
cutNode(node);
insertHead(node);
}else{
Node node=new Node(key,value);
map.put(key,node);
insertHead(node);
// 插入map和链表后,如果超过capacity
if(map.size()>capacity){
// 需要保存一下删除的node
Node delNode=cutTail();
// 在map中移除
map.remove(delNode.key);
}
}
}
public Node cutTail(){
Node temp=tail.pre;
temp.pre.next=tail;
tail.pre=temp.pre;
temp.next=null;
temp.pre=null;
return temp;
}
public void cutNode(Node node){
node.pre.next=node.next;
node.next.pre=node.pre;
node.next=null;
node.pre=null;
}
public void insertHead(Node node){
Node temp=head.next; //记录下 头结点的下一个节点
head.next=node;
node.next=temp;
node.pre=head;
temp.pre=node;
}
class Node{
int key;
int val;
Node pre;
Node next;
Node(int _key,int _val){
this.key=_key;
this.val=_val;
}
}
}排序链表【LC 148题】
给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。
class Solution {
public ListNode sortList(ListNode head) {
// 递归返回条件:节点为null,或者只有一个节点
if(head==null||head.next==null) return head;
ListNode fast=head.next;
ListNode slow=head;
while(fast!=null && fast.next!=null){
fast=fast.next.next;
slow=slow.next;
}
ListNode head2=slow.next;
slow.next=null; //打断链表
// 分别对前半段链表 和 后半段链表进行排序
ListNode left=sortList(head);
ListNode right=sortList(head2);
// 合并两个有序链表,因为不知道头结点是哪个,所以我们用一个虚拟头结点
ListNode dummy=new ListNode(0);
ListNode temp=dummy;
while(left!=null && right!=null){
if(left.val<=right.val){
temp.next=left;
left=left.next;
}else{
temp.next=right;
right=right.next;
}
temp=temp.next;
}
temp.next= left==null?right:left;
return dummy.next;
}
}反转链表||【LC 92题】
给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。
class Solution {
public ListNode reverseBetween(ListNode head, int left, int right) {
ListNode dummy=new ListNode(0,head);
ListNode p0=dummy;
for(int i=0;i<left-1;i++){
p0=p0.next;
}
// 先把中间部分反转
ListNode pre=null,cur=p0.next,next;
for(int i=0;i<right-left+1;i++){
next=cur.next;
cur.next=pre;
pre=cur;
cur=next;
}
// 再把两边接上
p0.next.next=cur;
p0.next=pre;
return dummy.next;
}
}分隔链表【LC 86题】
给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。
你应当 保留 两个分区中每个节点的初始相对位置。
不会,保留一下
回文链表【LC 234题】
给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。
笔试写法
利用栈先入后出的特性,遍历链表,push节点。判断:从栈中弹出节点,和原来的链表做比对。
- 时间复杂度:O(n)
- 额外空间复杂度:O(n)
public boolean isPalindrome(ListNode head) {
Stack<ListNode> stack=new Stack<>();
ListNode cur=head;
while(cur!=null){
stack.push(cur);
cur=cur.next;
}
while(!stack.isEmpty()){
if(stack.pop().val!=head.val) return false;
head=head.next;
}
return true;
}public boolean isPalindrome(ListNode head) {
ListNode fast=head.next,slow=head;
while (fast!=null&&fast.next!=null){
fast=fast.next.next;
slow=slow.next;
}
ListNode right=reverse(slow.next);
ListNode left=head;
while (right!=null){
if (right.val!=left.val) return false;
right=right.next;
left=left.next;
}
return true;
}
public ListNode reverse(ListNode head){
ListNode cur=head;
ListNode pre=null,next=null;
while (cur!=null){
next=cur.next;
cur.next=pre;
pre=cur;
cur=next;
}
return pre;
}合并两个有序链表【LC 21题】
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
class Solution {
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
ListNode dummy=new ListNode(0,list1);
ListNode cur=dummy;
ListNode A=list1,B=list2;
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 dummy.next;
}
}合并k个升序链表【LC 23题】
🏷 堆
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
Queue<ListNode> pq=new PriorityQueue<>((v1,v2)->v1.val-v2.val);
for(ListNode node:lists){
if(node!=null){
pq.offer(node);
}
}
ListNode dummy=new ListNode(0);
ListNode temp=dummy;
while(!pq.isEmpty()){
ListNode node=pq.poll();
temp.next=node;
if(node.next!=null){
pq.offer(node.next);
}
temp=temp.next;
}
return dummy.next;
}
}删除排序链表中的重复元素【LC 83题】
给定一个已排序的链表的头 head , 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 。
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if(head==null) return head;
ListNode cur=head;
while(cur.next!=null){
if(cur.val==cur.next.val){
cur.next=cur.next.next;
}else{
cur=cur.next;
}
}
return head;
}
}删除链表的倒数第N个节点【LC 19题】
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy=new ListNode(0,head);
ListNode fast=dummy;
ListNode slow=dummy;
while(n>0){
fast=fast.next;
n--;
}
while(fast.next!=null){
fast=fast.next;
slow=slow.next;
}
slow.next=slow.next.next;
return dummy.next;
}
}旋转链表【LC 61题】
给你一个链表的头节点 head ,旋转链表,将链表每个节点向右移动 k **个位置。
class Solution {
public ListNode rotateRight(ListNode head, int k) {
if(head==null || k==0) return head;
int len=0;
ListNode dummy=new ListNode(0,head);
ListNode fast=head;
while(fast!=null){
fast=fast.next;
len++;
}
k=k%len;
if(k==0) return head;
ListNode slow=dummy;
fast=dummy;
while(k>0){
fast=fast.next;
k--;
}
while(fast.next!=null){
fast=fast.next;
slow=slow.next;
}
ListNode newHead=slow.next;
slow.next=null;
fast.next=head;
return newHead;
}
}前置问题 荷兰国旗问题【LC 75题】
给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums ,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
用三个变量来维护:下一个0应该在的位置left,下一个2应该在的位置right,当前遍历位置idx。也就是[0,left-1]为0;[left,right-1]为1,[right,len-1]为2。【idx,r】为待处理区间。
- 当nums[idx]==1,表明它已经在正确的位置,因此我们只需要将
idx向右移动。- 当nums[idx]==0,说明它应该放在数组的左边,因此我们将其与
l位置的元素交换,并将l和idx都向右移动。- 当nums[idx]==2,说明它应该放在数组的右边,因此我们将其与
r位置的元素交换,并将r向左移动。由于交换后idx位置的新元素还未检查,所以idx暂时不动。
注意:
退出循环条件为idx>right。【idx,r】为待处理区间。退出循环时表明没有待处理的元素。
public class SortColors_75 {
public void sortColors(int[] nums) {
int left=0,idx=0,right=nums.length-1; // 左闭右闭
while(idx<=right){ //退出循环条件为idx>right
if(nums[idx]==0) {
swap(nums,idx++,left++);
}
else if (nums[idx]==1) idx++;
else swap(nums,idx,right--);
}
}
public void swap(int []nums,int left,int right){
int temp=nums[left];
nums[left]=nums[right];
nums[right]=temp;
}
}单链表荷兰国旗问题
将单向链表按某partition值划分成左边小、中间相等、右边大的形式,并保证节点相对位置不变。
- 需要6个变量构成3个单链表
- 小于partition链表的头指针
- 小于partition链表的尾指针
- 等于partition链表的头指针
- 等于partition链表的尾指针
- 大于partition链表的头指针
- 大于partition链表的尾指针
- 遍历链表,判断当前节点应当插入哪个链表
- 遍历结束,将三个链表依次首尾相接
注意:
需要考虑三个链表中为空的情况,不然可能会出现空指针异常
public static ListNode listPartition(ListNode head,int partition){
ListNode SH=null;
ListNode ST=null;
ListNode EH=null;
ListNode ET=null;
ListNode BH=null;
ListNode BT=null;
ListNode cur;
//遍历链表
while (head!=null){
//下面这两部操作,是为了三个链表的结尾都为null
cur=head.next;
head.next=null;
if (cur.val<partition){
if (SH==null){
SH=cur;
ST=cur;
}else {
ST.next=cur;
ST=cur;
}
} else if (cur.val == partition) {
if (EH==null){
EH=cur;
ET=cur;
}else {
ET.next=cur;
ET=cur;
}
}else {
if (BH==null){
BH=cur;
BT=cur;
}else {
BT.next=cur;
BT=cur;
}
}
head=cur;
}
//连接 三个链表 考虑链表为空的情况
if (ST!=null){
//如果小于partition链表不为空,就先直接赋值
ST.next=EH;
//再考虑等于partition链表是否为空的情况
ET=ET==null? BH :ST;
}
//先连接再说,后面再判断返回什么
if (ET!=null){
ET.next=BH;
}
return SH!=null ? SH : (EH!=null ? EH :BH);
}随机链表的复制【LC 138题】
给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。
例如,如果原链表中有 X 和 Y 两个节点,其中 X.random --> Y 。那么在复制链表中对应的两个节点 x 和 y ,同样有 x.random --> y 。接收原链表的头节点,返回复制链表的头节点。
public Node copyRandomList(Node head) {
HashMap<Node,Node> map=new HashMap<>();
Node cur=head;
// 构建 原节点-新节点的map
while (cur!=null){
map.put(cur,new Node(cur.val));
cur=cur.next;
}
cur=head;
//建立 next和random关系
while (cur!=null){
map.get(cur).next=map.get(cur.next);
map.get(cur).random=map.get(cur.random);
cur=cur.next;
}
return map.get(head);
}public Node copyRandomList(Node head) {
if(head==null) return head;
Node cur=head;
//先复制节点 A->B->C 变成 A->A'->B->B'->C->C'
while (cur!=null){
Node next=cur.next;
cur.next=new Node(cur.val);
cur.next.next=next;
cur=next;
}
//建立 random 关系
cur=head;
while (cur!=null){
cur.next.random=cur.random==null? null : cur.random.next;
cur=cur.next.next;
}
//split
cur=head;
Node headNew=cur.next;
Node curNew=cur.next;
while (cur!=null){
cur.next=cur.next.next;
cur=cur.next;
curNew.next= cur==null? null :cur.next;
curNew=curNew.next;
}
return headNew;
}两两交换链表中的节点【LC 24题】
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
class Solution {
public ListNode swapPairs(ListNode head) {
if(head==null || head.next==null) return head;
ListNode second=head.next;
ListNode temp=swapPairs(second.next);
head.next=temp;
second.next=head;
return second;
}
}环形链表【LC 141题】
给你一个链表的头节点 head ,判断链表中是否有环。
public boolean hasCycle(ListNode head) {
if(head==null) return false;
ListNode fast=head.next;
ListNode slow=head;
while(fast!=slow){
if(fast==null||fast.next==null)
return false;
fast=fast.next.next;
slow=slow.next;
}
return true;
}环形链表||【LC142题】
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
- 和上题类似,使用快慢指针
- 找环的入口时,固定方法:
public ListNode detectCycle(ListNode head) {
if(head==null) return null;
ListNode fast=head.next;
ListNode slow=head;
while(fast!=slow){
if(fast==null || fast.next==null) return null;
fast=fast.next.next;
slow=slow.next;
}
slow=slow.next;
fast=head;
while(fast!=slow){
fast=fast.next;
slow=slow.next;
}
return slow;
}以上是一些铺垫,考虑 相交链表 这个大问题。
相交链表【LC 160题】
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。
解题思想:
求出两个链表的长度差n让长的链表先走n步两个链表一起往后走,如果两链表相交,两者会同时走到交点;如果不相交,两者会同时走到null
//两个链表都无环
public ListNode getIntersectionNode1(ListNode headA, ListNode headB) {
int n=0;
ListNode curA=headA;
ListNode curB=headB;
//求出链表A和链表B的长度差
while (curA!=null){
curA=curA.next;
n++;
}
while (curB!=null){
curB=curB.next;
n--;
}
curA=n>0?headA:headB; //curA保存长的链表
curB=curA==headA?headB:headA;
n=Math.abs(n);
// 长链表先往前走n步
while (n>0){
curA=curA.next;
}
// 如果两链表相交,两者会同时走到交点;如果不相交,两者会同时走到null
while (curA!=curB){
curA=curA.next;
curB=curB.next;
}
return curA;
}相交链表【两个链表都有环】
分为三种情况:
- 两个有环链表不相交
- 两个链表共用一个入环节点
- 两个链表共用一个环,但是入环节点不一致
解决方案:
//两个链表都有环
public ListNode getIntersectionNode2(ListNode headA, ListNode headB) {
ListNode curA=headA;
ListNode loop1=detectCycle(headA); //链表A的环入口
ListNode curB=headB;
ListNode loop2=detectCycle(headB); //链表B的环入口
if (loop1==loop2){
int n=0;
while(curA!=loop1) n++;
while (curB!=loop2) n--;
curA = n>0 ? headA : headB;
curB = curA==headA ? headB : headA;
n=Math.abs(n);
while (n>0){
curA=curA.next;
n--;
}
while (curA!=curB){
curA=curA.next;
curB=curB.next;
}
return curA;
}else {
curA=loop1.next;
while (curA!=loop1){
if (curA==loop2) return loop2;
curA=curA.next;
}
}
return null;
}