链表
1,线性表
线性存储结构 一根线能串起来的数组 存储到物理空间中
数据需要有相同的数据类型
元素直接的关系 需要是一对一
分为有头结点的链表和没有头结点的链表
插入的时候 分为头插法和尾插法
节点的关系 称之为前置节点和后继节点的引用
2,链表中倒数第k个节点
分析:
解法1》
先遍历出链表的总长度n 倒数第k个节点=从头遍历的第n-k+1个节点
public static ListNode1 getKthFromEnd(ListNode1 head , int k){
int n = 0;
//先求出链表总长度
ListNode1 tmp = head;
while(tmp.next != null){
n++;
tmp = tmp.next;
}
//再次遍历 找到第n-k+1个节点
tmp = head;
for (int i = 0; i < n-k+1; i++) {
tmp = tmp.next;
}
return tmp;
}
解法2》
先定义额外指针 找到正数第K个节点
两个指针同事向右移动 当快的指针到达链表最后一个节点时
慢的指针 正好到达倒数第k个节点(相当于慢的指针没走的k步 由快的指针帮忙走完了
public static ListNode1 getKthFromEnd1(ListNode1 head , int k){
//快慢指针
ListNode1 slow = head;
ListNode1 fast = head;
//正数第k个节点
for (int i = 1; i < k; i++) {
fast = fast.next;
}
System.out.println(fast.val);
while(fast.next != null){
slow = slow.next;
fast = fast.next;
System.out.println("slow移动到"+slow.val+",fast移动到"+fast.val);
}
return slow;
}
让一个节点的next指向前置节点 2-》1
让元next节点指向自身3-》2
给你单链表的头节点 head
,请你反转链表,并返回反转后的链表。
示例 1:
输入:head = [1,2,3,4,5]输出:[5,4,3,2,1]
示例 2:
输入:head = [1,2]输出:[2,1]
示例 3:
输入:head = []输出:[]
在引用更改时,会丢失对原next节点的引用,需要用临时的指针tmp记录一下
public static ListNode1 reverseList(ListNode1 head) {
//记录前置节点和当前节点
ListNode1 pre = null;
ListNode1 cur = head;
while (cur != null){
ListNode1 tmp = cur.next;
//更改指针
cur.next = pre;
//pre和cur向后移动
pre = cur;
cur = tmp;
}
return pre;
}
4,双链表
1,链表与数组区别
优点:链表实现了真正的动态,不需要处理固定容量带来的系列问题
缺点:失去随机访问的能力
链表增删快,查询慢;数组查询快,增删慢
2,结构
单链表 数据+下一个节点的引用
双链表 数据+下一个节点的引用+对上一个节点的引用
java的经典实现--LinkedList
查询方式
根据元素索引位置找到元素
1》判断是否有环
给定一个链表,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos
是 -1
,则在该链表中没有环。注意:pos
不作为参数进行传递,仅仅是为了标识链表的实际情况。
如果链表中存在环,则返回 true
。 否则,返回 false
。
进阶:
你能用 O(1)(即,常量)内存解决此问题吗?
示例 1:
输入:head = [3,2,0,-4], pos = 1输出:true解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0输出:true解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1输出:false解释:链表中没有环。
分析:
环形链表 其实是形成了一个圈
跑步 快的人和慢的人在某一个点相遇 这能倒推出跑道是环形的
“快慢指针” 构造一个遍历时 走一步的指针 为慢指针
public static boolean hasCycle(ListNode1 head){
//如果链表本身不能为空 或者只有一个节点也是五环的
if(head == null || head.next == null)
return false;
ListNode1 slow = head;
ListNode1 fast = head;
while (true){
//没有环时 fast会先走到链表尾部
if(fast == null || fast.next == null){
return false;
}
slow = slow.next;
fast = fast.next.next;
if(slow == fast){
return true;
}
}
}
2》. 求环的入口节点
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null
。
为了表示给定链表中的环,我们使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos
是 -1
,则在该链表中没有环。注意,pos
仅仅是用于标识环的情况,并不会作为参数传递到函数中。
说明:不允许修改给定的链表。
进阶:
- 你是否可以使用
O(1)
空间解决此题?
示例 1:
输入:head = [3,2,0,-4], pos = 1输出:返回索引为 1 的链表节点解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0输出:返回索引为 0 的链表节点解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1输出:返回 null解释:链表中没有环。
分析:
快慢指针
第一次相遇之后,让fast重新指向头结点head,slow保持不变,fast和slow按照相同的速度移动,第二次相遇后,此节点即为入口节点。
【3,2,0,-4】
Slow 3 2 0 -4 走了三部
Fast 3 0 2 -4 走了六部(是slow的2倍)
fast-slow的步数 其实是环的长度
因为头结点 途径的路程 是从头结点除非 经过入口节点 到达相遇点的
头结点到入口节点的距离为一步
因为环的长度已知
所以从相遇再行进多声部 能回到入口节点= 环的长度-相遇点距离入口节点的长度
public static ListNode1 detectCycle(ListNode1 head) {
//如果链表本身不能为空 或者只有一个节点也是五环的
if(head == null || head.next == null)
return null;
ListNode1 slow = head;
ListNode1 fast = head;
while (true){
//没有环时 fast会先走到链表尾部
if(fast == null || fast.next == null){
return null;
}
slow = slow.next;
fast = fast.next.next;
if(slow != null){
System.out.print("slow 走到"+slow.val);
}
if(fast != null){
System.out.println(",fast走到"+fast.val);
}
if(slow == fast){
break;
}
}
//第一次相遇之后,让fast重新指向头结点head,slow保持不变,fast和slow按照相同的速度移动,第二次相遇后,此节点即为入口节点。
fast = head;
while (true){
slow = slow.next;
fast = fast.next;
if(slow != null){
System.out.print("slow走到"+slow.val);
}
if(fast != null){
System.out.println(",fast走到"+fast.val);
}
if(slow == fast)
return slow;
}
}
3》约瑟夫环
有N个人围成一圈,从第一个人开始报数,报道M被杀,然后下一个人继续从1开始报数,循环往复,直到剩下最后一个,最后一个人的初始位置在哪里?
分析:
1》环形链表法
将每个人试做链表中的一个节点,当报数到m的时候,删除节点,直到剩下最后一个节点
当找到报数m-1的节点 node node.next = node.next.next;
当只剩最后一个节点时候 node.next = node;
public static int josephus(int n, int m){
//初始化环形 链表
int[] arr = new int[n];
for (int i = 0; i < arr.length; i++) {
arr[i] = i + 1;
}
ListNode1 node1 = ListNode1.arrayToListNode(arr);
ListNode1.toCycle(node1,0);
/**
*将每个人试做链表中的一个节点,当报数到m的时候,删除节点,直到剩下最后一个节点
* 当找到报数m-1的节点 node node.next = node.next.next;
* 当只剩最后一个节点时候 node.next = node;
**/
int cnt = 1;
while (node1.next != node1){
if(cnt == m-1){
System.out.println("删除节点"+node1.next.val);
node1.next = node1.next.next;
cnt = 0;
}
node1 = node1.next;
cnt++;
if(node1.next == node1)
return node1.val;
}
return 0;
}
2》数据遍历法
将每个人视作数组中的一个元素,当报数到m的时候,使用占位符代表此人撕掉了
当剩余人数 只有一个人时候 停止循环
public static int josephus1(int n,int m){
//数组记录 初始是0 使用-1代表当前元素 死掉
int[] people = new int[n];
//人的索引
int index = -1;
//报数1 2 .....m
int cnt = 0;
//剩余人数
int remain = n;
while (remain > 0){
index++;
//到达数组末端 重新从头遍历
if(index >= n)
index = 0;
//如果此人撕掉 跳过 继续报数
if(people[index] == -1){
continue;
}
//报数到m 将index对应位置的元素 职位-1 (尸体)
if(cnt == m){
people[index] = -1;
cnt = 0;
remain--;
}
cnt++;
}
return index;
}