链表合集
1.单双链表的定义
class ListNoe{
int val;
ListNode next;
ListNode(){
}
ListNode(int val){
this.val=val;
}
}
class DoubleNoe{
int val;
DoubleNoe next;
DoubleNoe pre;
DoubleNoe(){
}
DoubleNoe(int val,DoubleNoe pre,DoubleNoe next){
this.val=val;
this.pre=pre;
this.next=next;
}
}
2.链表常用到的数据结构
- 栈(Stack): 先进后出(反转链表)
- 哈希表(Map): 记录next的指向(复杂链表的复制)
- 堆(heap) : 可用于链表节点的排序(合并 K 个升序链表)
3.算法例题
3.1 反转链表
反转系列总结操作步骤:
- 记录下一个节点 next
- 完成当前节点的属性更改操作,更改指向 + 更改值
- 继续递归的状态更改
- leetcode 例题 : 反转链表
ListNode reverse(ListNode head){
ListNode pre=null;
ListNode next=null;
while(head!=null){
// 1.记录下一个节点
next=head.next;
// 2.当前节点的操作
head.next=pre;
// 3.继续递归,下一步
pre=head;
head=next;
}
return pre;
}
3.2 反转双链表
ListNode reverseDouble(DoubleNode head){
DoubleNode pre=null;'
DoubleNode next=null;
while(head!=null){
// 1.记录下一个节点
next=head.next;
// 2.当前节点的操作
head.next=pre;
head.last=next;
// 3.继续递归,下一步
pre=head;
head=next;
}
return pre;
}
3.3 回文链表
- leetcode 例题 : 回文链表
3.3.1 栈实现
public boolean isPalindrome(ListNode head) {
Stack<Integer> stack=new Stack();
ListNode cur=head;
while(cur!=null){
stack.add(cur.val);
cur=cur.next;
}
while(!stack.isEmpty()){
if(head.val!=stack.pop()){
return false;
}
head=head.next;
}
return true;
}
3.3.2 反转后半部分链表实现(无额外空间消耗)
boolean isPalindrome(ListNode head){
//1.快慢指针来划分两边
ListNode slow=head,fast=head;
while(fast!=null && fast.next!=null){
slow=slow.next;
fast=fast.next.next;
}
//2.将右边部分反转
ListNode p=head,q=reverse(slow);
ListNode t=q;
//3.左边从左向右,右边从右向左依次比较
while(p!=null && q!=null){
if(p.val!=q.val){
return false;
}
p=p.next;
q=q.next;
}
//4.还原右边部分
t=reverse(t);
slow.next=t;
return true;
}
3.4 删除链表倒数第N个节点
思路 : 递归到倒数第N个节点,使其前驱节点pre.next=cur.next (需要注意的是,当删除头节点的时候。我们初始化一个头节点的前驱节点即可)
- leetcode 例题 : 删除链表倒数第N个节点
class Solution {
int n;
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode pre=new ListNode(-1);
pre.next=head;
this.n=n;
dfs(pre,head);
return pre.next;
}
void dfs(ListNode pre,ListNode cur){
if(cur==null){
return;
}
dfs(cur,cur.next);
n--;
if(n==0){
pre.next=cur.next;
return;
}
}
}
3.5 链表分区
思路 : 创建6个指针,分别为小于区,等于区,大于区的头尾指针,最后将,小,等,大区的头尾指针依次相连(需要判断区间尾巴为空的情况)
- leetcode 例题 : 链表分区 ,不同的是本代码划分了三个区域
public ListNode partition(ListNode head, int x) {
ListNode sh=null,eh=null,gh=null;
ListNode sl=null,el=null,gl=null;
while(head!=null){
if(head.val>x){
if(gh==null){
gh=head;
gl=head;
}else{
gl.next=head;
gl=gl.next;
}
}else if(head.val<x){
if(sh==null){
sh=head;
sl=head;
}else{
sl.next=head;
sl=sl.next;
}
}else{
if(eh==null){
eh=head;
el=head;
}else{
el.next=head;
el=el.next;
}
}
head=head.next;
}
//将小于区尾指针和等于区头指针相连,将等于区尾指针和大于区头指针相连
if(sl!=null){
sl.next=eh;
el = el==null ? gh : el;
}
if(el!=null){
el.next=gh;
}
return sh!=null ? sh : (eh!=null ? eh : gh);
}
3.6 复制有随机节点的单链表
- leetcode 例题 : 复制有随机节点的单链表
public Node copyRandomList(Node head) {
// map 记录原来的 random和next 指针的指向
HashMap<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(p).next = map.get(p.next);
map.get(p).random = map.get(p.random);
p=p.next;
}
return map.get(head);
}
3.7 单链表相交问题
相交问题 : 走到尾巴就换另一个链表的头,判断是否相等
- leetcode 例题 : 单链表相交问题
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode a=headA,b=headB;
while(a!=b){
a= a==null ? headB : a.next;
b= b==null ? headA : b.next;
}
return a;
}
3.8 单链表相交成环问题
- 问题描述 : 给定两个可能有环也可能无环的单链表,头节点head1、head2.实现一个函数,如果两个链表相交,请返回相交的第一个节点,不相交返回null
ListNode doubleRing(ListNode head1,ListNode head2){
// 获取两个单链表第一个入环的节点
ListNode a=getIntersectionNode(head1),b=getIntersectionNode(head2);
// 1.两个单链表无环要相交,一定是最后部分重合(2.1情况)
if(a==null && b==null){
ListNode p=head1,q=head2;
while(p!=q){
p= p==null? head2 : p.next;
q= q==null ? head1 : q.next;
}
return q;
}
// 2.一个有环和一个无环的单链表不可能重合
// 3.两个链表都有环
else{
//3.2 两个入环节点是同一个(只管入环前的几个节点,同样是两个单链表无环相交)(如图中间情况)
if(a==b){
ListNode p=head1,q=head2;
while(p!=q){
p= p==a ? head2 : p.next;
q= q==q ? head1 : q.next;
}
return q;
}
else{
ListNode cur=a.next;
while(cur!=a){
//3.3 入环节点不同(如图右边情况)
if(cur==b){
return cur;
}
cur=cur.next;
}
//3.1 两个有环但不相交(如图左边情况)
return null;
}
}
}
3.9 链表排序
思路 :
-
快慢指针分区
-
切断两个区域
-
两个区域排好序
-
两个区域合并
-
leetcode 例题 : 链表排序
public ListNode sortList(ListNode head) {
if(head==null || head.next==null) return head;
ListNode fast=head.next,low=head;
// 1.快慢指针分区
while(fast!=null && fast.next!=null){
fast=fast.next.next;
low=low.next;
}
ListNode t=low.next;
// 2. 切断两个区域
low.next=null;
// 3. 两个区域排好序
ListNode left=sortList(head);
ListNode right=sortList(t);
// 4. 两个区域合并
return merge(left,right);
}
ListNode merge(ListNode left, ListNode right){
ListNode head=new ListNode(-1);
ListNode p=head;
while(left!=null && right!=null){
if(left.val < right.val){
p.next=left;
left=left.next;
}else{
p.next=right;
right=right.next;
}
p=p.next;
}
if(left==null){
p.next=right;
}else{
p.next=left;
}
return head.next;
}
3.10 合并 K 个升序链表
思路 :
- 用堆维护K 个升序链表的头节点
- 每次弹出最小的一个节点
- 把弹出节点的下一个节点加入堆中
- 直到堆为空
leetcode 例题 : 链表排序
public ListNode mergeKLists(ListNode[] lists) {
//小根堆
PriorityQueue<ListNode> queue=new PriorityQueue<ListNode>((ListNode a,ListNode b)->{return a.val-b.val;});
//维护所有的头节点,然后弹出最小的节点
for(ListNode head : lists){
if(head!=null){
queue.add(head);
head=head.next;
}
}
ListNode head=new ListNode(-1);
ListNode p=head;
while(!queue.isEmpty()){
ListNode node=queue.poll();
p.next=node;
if(node.next!=null){
queue.add(node.next);
}
p=p.next;
}
return head.next;
}
观众老爷多多点赞 + 关注。持续跟新中!!!!