文章目录
LinkedList
1.基本介绍
1.LinkedList的底层是双向链表结构,由于链表没有将元素存储在连续的空间中,元素存储在单独的节点中,然后通过引用将节点连接起来了,因此在在任意位置插入或者删除元素时,不需要搬移元素,效率比较高。
2.结构如图所示:
3.在集合框架中,LinkedList也实现了List接口,具体如下:
【说明】
- LinkedList实现了List接口
- LinkedList的底层使用了双向链表
- LinkedList没有实现RandomAccess接口,因此LinkedList不支持随机访问
- LinkedList的任意位置插入和删除元素时效率比较高,时间复杂度为O(1)
- LinkedList比较适合任意位置插入的场景
1.1常用方法
方法 | 解释 |
---|---|
boolean add(E e) | 尾插e |
void add(int index, E element) | 将 e 插入到 index 位置 |
boolean addAll(Collection<? extends E> c) | 尾插 c 中的元素 |
E remove(int index) | 删除 index 位置元素 |
boolean remove(Object o) | 删除遇到的第一个 o |
E get(int index) | 获取下标 index 位置元素 |
E set(int index, E element) | 将下标 index 位置元素设置为 element |
void clear() | 清空 |
boolean contains(Object o) | 判断 o 是否在线性表中 |
int indexOf(Object o) | 返回第一个 o 所在下标 |
int lastIndexOf(Object o) | 返回最后一个o 所在下标 |
List subList(int fromIndex, int toIndex) | 截取部分list |
1.2ArrayList和LinkedList区别
2.具体应用
2.1 LinkeList三种遍历
2.1.1 代码
//linkedlist的应用(遍历)
LinkedList<String> linkedList = new LinkedList<String>();
linkedList.add("a");
linkedList.add("b");
linkedList.add("c");
linkedList.add("d");
linkedList.add("e");
//直接输出
System.out.println(linkedList);
//for循环
for (String x : linkedList){
System.out.println(x);
}
//迭代器遍历(正向遍历
ListIterator<String> it = linkedList.listIterator();
while (it.hasNext()){
System.out.println(it.next());
}
//反向遍历
while (it.hasPrevious()){
System.out.println(it.previous());
}
2.2 相交链表
2.2.1题目描述
给你两个单链表的头节点 headA 和 headB,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。相交链表
图示两个链表在节点 c1 开始相交:
2.2.2 解题思路
简单情况:
链表长度相同,定义两个指针同时遍历地址相同即为交点
其他情况:
链表长度不相同,同时遍历并不相交
解决办法:可以计算出两个链表长度差值,长的先走完差值,再同时遍历即可
代码
/**
* 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) {
if(headA == null && headB == null) {
return null;
}
//1、分别求两个链表的长度
int lenA = 0;
int lenB = 0;
ListNode pl = headA;
ListNode ps = headB;
while (pl != null) {
lenA++;
pl = pl.next;
}
while (ps != null) {
lenB++;
ps = ps.next;
}
//2. 要指回来
pl = headA;
ps = headB;
int len = lenA - lenB;//大小
//3、根据Length的值 修改指向
if(len < 0) {
pl = headB;
ps = headA;
len = lenB-lenA;
}
//1. len一定是一个正数 2.pl指向的链表一定是最长的 ps 指向的链表一定是最短的
while (len != 0) {
pl = pl.next;
len--;
}
while (pl != ps) {
pl = pl.next;
ps = ps.next;
}
//pl == ps
/* if(pl == null) {
return null;
}
return pl;*/
return pl;
}
}
2.3 环形链表(是否有环)
2.3.1题目描述
给你一个链表的头节点 head ,判断链表中是否有环。
如果链表中存在环 ,则返回 true 。 否则,返回 false 。环形链表
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
2.3.2 解题思路
转换为相遇问题,有环,一定会相遇。
定义快慢指针分别遍历链表,当快慢指针相遇时,即为环形链表。
此时定义快慢指针,所走步数为快指针两步,慢指针一步。
注意:此时快指针如果为3步…n步时可能会出现永远不会相遇的情况
如:极限可能,所走快的步数正好为一个圈,则永远不能相遇,犹豫一步不能构成一个圈,则看到会相遇。
2.3.3 代码
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public boolean hasCycle(ListNode head) {
//定义快慢指针,指向头节点
ListNode fast = head;
ListNode slow = head;
//快指针走两步,即fast不为空和fast.next不为空时
//(fast.next.next为空无意义,fast走一步即为fast.next为空)遍历
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if(fast == slow) {
return true;
}
}
return false;
}
}
2.4 环形链表(入口点)
2.4.1题目描述
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。不允许修改 链表。环形链表
2.4.2解题思路
在寻找相交环的基础上,寻找入口点
存在技巧:
多圈则C非常小。
即 X == Y
则此时slow从起始点开始走,fast从相遇点开始走,相遇时即为入口点。
代码
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
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) {
//得到相遇点,即为fast此时指向节点
break;
}
}
if(fast == null || fast.next == null) {
return null;
}
//慢指针回到头节点,同时遍历,相遇时即为入口点
slow = head;
while (fast != slow) {
fast = fast.next;
slow = slow.next;
}
return fast;
}
}
2.5 链表分割
2.5.1 题目描述
现有一链表的头指针 ListNode* pHead,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针。链表分割
2.5.2 解题思路
import java.util.*;
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
将链表分割为两个,定义四个节点 bs,be,as,ae分别表示两段链表的头和尾
本题可以采用,将小于x的节点头插到前面一段链表中,大于的放在后一段。但是需要考虑是否存在大于x的节点和小于x的节点。
}*/
public class Partition {
public ListNode partition(ListNode pHead, int x) {
// write code here
ListNode bs = null;
ListNode be = null;
ListNode as = null;
ListNode ae = null;
ListNode cur = pHead;
while (cur != null) {
//小于插到前一段
if(cur.val < x) {
//为空时,为bs、be赋值指向cur。
if(bs == null) {
bs = cur;
be = cur;
}else {
//不为空时插入be后面
be.next = cur;
be = be.next;
}
//大于插入后一段
}else {
为空时,为as、ae赋值指向cur。
if(as == null) {
as = cur;
ae = cur;
}else {
//插入ae后面
ae.next = cur;
ae = ae.next;
}
}
cur = cur.next;
}
// 有可能不会同时存在小于x 和 大于等于x 的数据
//不存在小于的时候,不能直接 be.next = as;
if(bs == null) {
return as;
}
//第一段不为空,连接起来(第二段为空不影响)
be.next = as;
//第2个段为空不为空的问题,加if判断为不为空,不能1直接将ae.next置空以免空指针异常
if(as != null) {
ae.next = null;
}
return bs;
}
}