1. ArrayList****的缺陷
上节课已经熟悉了ArrayList的使用,并且进行了简单模拟实现。通过源码知道,ArrayList底层使用数组来存储元 素:
由于其底层是一段连续空间,当在ArrayList任意位置插入或者删除元素时,就需要将后序元素整体往前或者往后
搬移,时间复杂度为****O(n),效率比较低,因此ArrayList****不适合做任意位置插入和删除比较多的场景。因此:java
集合中又引入了LinkedList,即链表结构。
**2.**链表
2.1****链表的概念及结构
链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的 。


注意:
1.从上图可看出,链式结构在逻辑上是连续的,但是在物理上不一定连续2.现实中的结点一般都是从堆上申请出来的3.从堆上申请的空间,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续
实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:

虽然有这么多的链表的结构,但是我们重点掌握两种:
无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如 哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多
这里我们这样定义链表一个节点
3.单链表的实现
java
static class ListNode{
public int vla;
public ListNode next;
public ListNode(int vla){
this.vla = vla;
}
}
链表的打印
java
public void display(){
ListNode cur = head;
while(cur!=null)
{
System.out.print(cur.vla+" ");
cur=cur.next;
}
}

cur会依次循环下去,依次打印
初始化链表
java
public void Createlist(){
ListNode node1 = new ListNode(1);
ListNode node2 = new ListNode(1);
ListNode node3 = new ListNode(1);
ListNode node4 = new ListNode(1);
node1.next = node2;
node2.next = node3;
node3.next = node4;
this.head = node1;
}
头插法

尾插法

java
public void addLast(int data){
ListNode newnode=new ListNode(data);
if(head==null){
head=newnode;
return;
}
else{
ListNode cur=head;
while(cur.next!=null){
cur=cur.next;
}
cur.next=newnode;
}
}
任意位置插入

java
public void addIndex(int index,int data){
if(index<0||index>size()){
throw new IndexOutOfBoundsException();
}
ListNode newnode=new ListNode(data);
ListNode cur=head;
if(index==0){
addFirst(data);
return;
}
if(index==size()){
addLast(data);
return;
}
int i=0;
while(i<index){
cur=cur.next;
i++;
}
newnode.next=cur.next;
cur.next=newnode;
}
删除第一次出现关键字为key的节点

java
public void remove(int key){
if(head==null){
return;
}
if(head.vla==key){
head=head.next;
return;
}
ListNode cur=find(key);
if(cur==null){
return;
}
ListNode del=cur.next;
cur.next=del.next;
}
删除所有值为key的节点

java
public void removeAllKey(int key){
if(head==null){
return;
}
ListNode current = head.next;
ListNode prev=head;
while(current!=null){
if(current.vla==key){
prev.next=current.next;
current=current.next;
}
else{
prev=current;
current=current.next;
}
if(head.vla==key){
head=head.next;
}
}
清空链表

java
public void clear(){
ListNode cur = head;
while(cur!=null){
ListNode temp=cur.next;
cur.next=null;
cur=temp;
}
head=null;
}
链表反转


java
public ListNode reverseList(ListNode head) {
if(head==null){
return head;
}
ListNode cur=head.next;
head.next=null;
while(cur!=null){
ListNode prev=cur.next;
cur.next=head;
head=cur;
cur=prev;
}
return head;
}
给定一个带有头结点 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点 使用快慢指针解决

java
public ListNode middleNode(ListNode head) {
if(head==null){
return head;
}
ListNode fast=head;
ListNode slow=head;
while(fast!=null&&fast.next!=null){
fast=fast.next.next;
slow=slow.next;
}
return slow;
}
返回倒数第k个值
java
public int kthToLast(ListNode head,int k){
if(head==null){
return -1;
}
if(k<=0) return -1;
ListNode fast=head;
ListNode slow=head;
int c=0;
while(c!=(k-1)){
fast=fast.next;
if(fast.next==null){
return -1;
}
c++;
}
while(fast.next!=null){
slow=slow.next;
fast=fast.next;
}
return slow.vla;
}
合并两个有序链表

最开始我们定义一个newH用来保存这个链表头结点
我们的思路是首先定义一个零时变量temp,然后呢比较heada.val和headb.val的大小,
如果headb的val大那我们就将heada赋给temp.next,heada往前走一步即 heada=heada.next
temp往前走一步,即temp=temp.next
如果heada的val值大那我们就将headb赋给temp.next,headb往前走一步即 headb=headb.next
temp往前走一步,即temp=temp.next
循环上面的操作,然后直到链表a或者链表b为空,此时我们还没有结束,temp的next指针还是空,需要把剩下的连接起来
最后返回newH
java
public ListNode mergeTwoLists(ListNode heada, ListNode headb) {
//合并两个链表
ListNode p=new ListNode(-1);
//开始遍历两个链表
ListNode temp=p;
while (heada!=null&&headb!=null){
if(heada.val<headb.val){
temp.next=heada;
heada=heada.next;
temp=temp.next;
}
else {
temp.next=headb;
temp=temp.next;
headb=headb.next;
}
}
if(heada!=null){
temp.next=heada;
}
if(headb!=null){
temp.next=headb;
}
return p.next;
}
. 编写代码,以给定值x为基准将链表分割成两部分,所有小于x的结点排在大于或等于x的结点之前

这个题目我们定义两个链表bs,as,同时在定义两个指针be,ae,给定一个值,把原来链表小于这个值x的放到bs,大于这个值x放到as,同时be和ae指向链表尾部
最后遍历完上面的链表以后,我们开始将两个链表连接,返回bs,但是有一点需要注意,我们要判断bs是否为空,如果bs为空,那么所有的元素都比关键值x大,所有的元素放在as里面,只需要返回as,随后不管as是否为空,他的尾部应该置为空
java
public ListNode partition(ListNode pHead, int x) {
// write code here
ListNode cur = pHead;
ListNode bs = null;
ListNode be = null;
ListNode as = null;
ListNode ae = null;
while (cur != null) {
if (cur.val < x) {
if (bs == null) {
bs = be = cur;
} else {
be.next = cur;
be = be.next;
}
cur = cur.next;
} else {
if (as == null) {
as = ae = cur;
} else {
ae.next = cur;
ae = ae.next;
}
cur = cur.next;
}
}
if (bs == null) {
return as;
}
be.next = as;
if(as!=null){
ae.next=null;
}
return bs;
}
回文链表


我们的思路是这样的,回文链表分为两种,一种奇数个数链表节点,一种偶数个链表节点,我们首先来看如果是奇数链表,我们首先使用快慢指针找到链表中间节点,然后开始定义一个指针cur=slow.next,,定义一个指针curn=cur.next用来保存下一个节点的地址,从slow往后开始反转链表,将cur.next=slow,slow和cur往后,slow=cur,cur=curn直到curn等于空
java
public boolean huiwen(){
ListNode fast=head;
ListNode slow=head;
while(fast.next!=null&&fast!=null){
slow=slow.next;
fast=fast.next.next;
}
ListNode cur=slow.next;
while(cur!=null){
ListNode curn=cur.next;
cur.next=slow;
slow=cur;
cur=curn;
}
while(head!=slow){
if(head.vla!=slow.vla){
return false;
}
if(head.next==slow){
return true;
}
head=head.next;
slow=slow.next;
}
return true;
}
相交链表

我们的思路是先求两个链表的长度,定义两个指针hl=heada,hs=headb,其中长的链表先走他们的差值,随后再让他们一起走,直到他们相交,即hl=hs,然后我们就返回其中一个指针,如果没有相交点,那么就返回null
java
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode hl=headA;
ListNode hs=headB;
int lena=0;
int lenb=0;
while(hl!=null){
hl=hl.next;
lena++;
}
while(hs!=null){
hs=hs.next;
lenb++;
}
hl=headA;
hs=headB;
int c=lena-lenb;
if(c<0){
hl=headB;
hs=headA;
c=lenb-lena;
}
for(int i=0;i<c;i++){
hl=hl.next;
}
while(hl!=hs){
hl=hl.next;
hs=hs.next;
}
if(hs==null){
return null;
}
return hl;
}
判断链表中是否有环
【思路】
快慢指针,即慢指针一次走一步,快指针一次走两步,两个指针从链表起始位置开始运行,如果链表 带环则一定会在环中相遇,否则快指针率先走到链表的末尾。比如:陪女朋友到操作跑步减肥。
【扩展问题】
为什么快指针每次走两步,慢指针走一步可以?
假设链表带环,两个指针最后都会进入环,快指针先进环,慢指针后进环。当慢指针刚进环时,可能就和快 指针相遇了,最差情况下两个指针之间的距离刚好就是环的长度。此时,两个指针每移动一次,之间的距离 就缩小一步,不会出现每次刚好是套圈的情况,因此:在慢指针走到一圈之前,快指针肯定是可以追上慢指
针的,即相遇。
快指针一次走3步,走4步,...n步行吗?

java
public boolean hasCycle(ListNode head) {
ListNode fast=head;
ListNode slow=head;
while(fast!=null&&fast.next!=null){
slow=slow.next;
fast=fast.next.next;
if(slow==fast){
return true;
}
}
return false;
}
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 NULL


java
public ListNode detectCycle(ListNode head) {
ListNode fast=head;
ListNode slow=head;
while(fast!=null&&fast.next!=null){
slow=slow.next;
fast=fast.next.next;
if(slow==fast){
break;
}
}
if(fast==null||fast.next==null){
return null;
}
slow=head;
while(slow!=fast){
slow=slow.next;
fast=fast.next;
}
return slow;
}
4.什么是LinkedList
LinkedList的底层是双向链表结构(链表后面介绍),由于链表没有将元素存储在连续的空间中,元素存储在单独的节 点中,然后通过引用将节点连接起来了,因此在在任意位置插入或者删除元素时,不需要搬移元素,效率比较高。
5.LinkedList****的模拟实现
初始化双链表
java
static class Node{
int data;
Node next;
Node prev;
public Node(int data){
this.data = data;
}
}
public Node head;
public Node tail;
// 2、无头双向链表实现
头插入数据
java
public void addFirst(int data){
if(head==null){
head=new Node(data);
}
Node temp=new Node(data);
temp.prev=head;
head.next=temp;
head=temp;
}
尾插入数据
java
public void addLast(int data) {
Node temp=new Node(data);
if (head == null) {
head = tail = temp;
} else {
tail.next = temp;
temp.prev = tail;
tail = tail.next;
}
}
指定位置插入
java
//任意位置插入,第一个数据节点为0号下标
public void addIndex(int index,int data){
if(index<0||index>size()){
return;
}
if(head==null){
head=new Node(data);
}
if(index==0){
addFirst(data);
return;
}
if(index==size()){
addLast(data);
return;
}
Node temp=new Node(data);
Node cur=head;
for(int i=0;i<index-1;i++){
cur=cur.next;
}
cur.next=temp;
temp.prev=cur;
}
检查是否包含关键字
java
//查找是否包含关键字key是否在单链表当中
public boolean contains(int key){
Node temp=head;
while(temp!=null){
if(temp.data==key){
return true;
}
}
return false;
}
删除第一次出现key的
java
//删除第一次出现关键字为key的节点
public void remove(int key){
Node cur=head;
while(cur!=null){
if(cur.data==key){
if(cur==head){
head=head.next;
if(head!=null){
head.prev=null;
}
}
else {
cur.prev.next = cur.next;
if(cur.next==null){
tail=tail.prev;
}
else {
cur.prev.next=cur.prev;
}
return;
}
}
cur=cur.next;
}
}
删除所有key值
java
//删除所有值为key的节点
public void removeAllKey(int key){
Node cur=head.next;
// while(cur!=null){
// if(cur.data==key){
// if(cur==head){
// head=head.next;
// if(head!=null){
// head.prev=null;
// }
// }
// else {
// cur.prev.next = cur.next;
// if(cur.next==null){
// tail=tail.prev;
// }
// else {
// cur.next.prev=cur.next;
// }
// }
//
// }
// cur=cur.next;
// }
while(cur.next!=null){
if(cur.data==key){
cur.prev.next = cur.next;
cur.next.prev=cur.next;
}
cur=cur.next;
}
if(head.data==key){
head=head.next;
if(head!=null){
head.prev=null;
}
}
if(tail.data==key){
tail=tail.prev;
}
}
双链表长度
java
//得到单链表的长度
public int size(){
Node cur=head;
int c=0;
while(cur.next!=null){
cur=cur.next;
c++;
}
return c;
}
显示链表
java
public void display(){
Node cur=head;
while(cur!=null){
System.out.print(cur.data+" ");
cur=cur.next;
}
}