关于链表
大多数链表都是检验coding能力的,通过熟练度不断提升。在面试中一二面会进行考察,基本必须AC,是很基础的考察项。
关于null
JVM虚拟机里面规定了一个地址为null,然后链表最后一个元素中其实也是存了一个地址,只不过该地址指向null所在的内存空间。所以最后一个元素指向null。(可以理解为启动前约定好,某一个地址就叫null)
单链表的反转及双链表的反转
- 链表的遍历
首先获得头指针,
循环Node cur = head; while (cur != null){ // 中间处理流程 System.out.println(cur.value); //切记不要忘了,更新下一个节点。while循环中一定要更新节点 cur = cur.next; }
- 反转注意事项:
定义一个反转方法后, 该方法一定要返回新的头指针,并在调用时进行接收。否则会被GC(引用为0即被GC)。 - 反转大体步骤
3.1 先用next记录下一个节点的地址
3.2 对当前节点的指针进行反转
3.3.将pre和当前节点head,进行更新 --> 往前移动
代码:
package class04;
public class LinkedList {
public static class Node {
public int value;
public Node next;
public Node(int value) {
this.value = value;
}
}
public static class DoubleNode {
public int value;
public DoubleNode last;
public DoubleNode next;
public DoubleNode(int value) {
this.value = value;
}
}
// 链表都是需要头节点来进行访问的
public static Node reverseLinkedList(Node head) {
Node pre = null;
Node next = null;
// 修改 Node 中Next指针指向Pre
// 顺序: 首先保存next, 然后修改指针指向pre,然后将pre和head一同更新到下一个位置
while (head != null) {
next = head.next;
head.next = pre;
pre = head;
head = next;
}
return pre;
}
public static DoubleNode reverseDoubleList(DoubleNode head) {
DoubleNode pre = null;
DoubleNode next = null;
while (head != null) {
// 1. 先把下一个节点位置 记住
next = head.next;
// 2. 修改指针 (此时可以随意修改指针)
head.last = next;
head.next = pre;
// 3.更新pre 和 head
pre = head;
head = next;
}
return pre;
}
public static void printLinkedList(Node head) {
while (head != null) {
System.out.println(head.value);
head = head.next;
}
}
public static void printDoubleList(DoubleNode head) {
System.out.println("Forward");
DoubleNode pre = null; // 栈中声明的变量需要赋初值
while (head != null) {
System.out.println(head.value);
pre = head;
head = head.next;
}
System.out.println("Reverse");
while (pre != null) {
System.out.println(pre.value);
pre = pre.last;
}
}
public static void main(String[] args) {
Node node1 = new Node(1);
node1.next = new Node(2);
node1.next.next = new Node(3);
// 1 -> 2 -> 3
System.out.println("Linked List original order:");
printLinkedList(node1);
System.out.println("Linked List reverse order:");
node1 = reverseLinkedList(node1);
printLinkedList(node1);
DoubleNode head = new DoubleNode(1);
head.next = new DoubleNode(2);
head.next.next = new DoubleNode(3);
head.next.last = head;
head.next.next.last = head.next;
// 1 -> 2 -> 3
System.out.println("Double Linked List original order:");
printDoubleList(head);
System.out.println("Double Linked List reverse order:");
head = reverseDoubleList(head);
printDoubleList(head);
}
}
单链表实现栈和队列
重点:
- 栈 : 后进先出
- 队列: 先进先出
那么,对于单链表而言,先出的必定使用head指向,所以根据谁先出,确定head位置。比如,栈,后进先出,那么Head指向后进的。队列,先进先出,那么Head指向先进的。
所以,
栈: 只需要head,头插法
队列: 需要head和tail, 尾插法
PS: 无论是头插法还是尾插法,都要进行(2个)分支判断,1.为空(head==null)直接赋值 2.为不空(head!=null) 要对新增元素进行指针调整.
关键点: 就是用head来指向 出值 的元素对象。
特点:
插入,弹出,查看 ----> 时间复杂度 都为O(1);
package class04;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack;
public class Code02_LinkedListToQueueAndStack {
// 首先定义 单链表节点 使用泛型
public static class Node<V> {
public V value;
public Node<V> next;
public Node(V value) {
this.value = value;
this.next = null;
}
}
// 队列需要 head 和 tail,并且为了随时可以返回size,还要一个属性为size
// 使用封装,private
// 总结: 单链表实现队列,头尾指针,尾插法
public static class MyQueue<V> {
private Node<V> head;
private Node<V> tail;
private int size;
// 构造器
public MyQueue() {
head = null;
tail = null;
size = 0;
}
public boolean isEmpty() {
return size == 0;
}
public int size() {
return size;
}
// 入队: 插入元素时, 将元素转化为节点对象, 然后调整首尾指针
public void offer(V value) {
Node<V> cur = new Node<>(value);
// 如果 队列 为空, 则将首尾指针一同指向节点
if (head == null) {
head = cur;
tail = cur;
} else {
// 如果 队列中已经有节点了,依据 队列先入先出 的原则,head指针指向先入的元素,因为出队顺序依据head指针,
// 先入先出,意味着 head指针 指向 最先进入的元素,后面的元素,通过尾插法插入
tail.next = cur;
tail = cur;
}
// 最后不要忘记 因为队列 新增了元素 , 所以要 size++
size++;
}
// 出队: 出队 意味着两件事情: 1. 返回队首中的首个元素 2.去除返回的元素
public V poll() {
// 首先定义 返回元素为null. 有两个作用,如果队列为空,直接返回;如果队列不空,会在if语句中进行赋值。
// 泛型针对的是 引用数据类型
V ans = null;
if (head != null) {
ans = head.value;
head = head.next;
size--;
}
// 要注意一个场景:队列中只有一个元素,此时出队,头指针为null,尾指针指向上一个元素
// 存在问题,1. 头尾指针没有及时统一 2.最重要一点: 若tail依然指向出队元素,该对象无法被JV,释放,从而出现脏数据,导致内存泄漏
// 所以要在每次出队之后进行检查,是否头指针变为了空
if (head == null) {
tail = null;
}
return ans;
}
// 返回队首元素的值,但是不移除该元素
public V peek() {
V ans = null;
// 两种情况,
// 1. 队列为空,不处理,【直接返回ans】
// 2. 队列不空(head != null), 对ans进行赋值,【返回ans】
if (head != null) {
ans = head.value;
}
return ans;
}
}
// 单链表实现栈,头指针+头插法 因为后入先出,后进入的先出,head意味着先出
public static class MyStack<V> {
private Node<V> head;
private int size;
// 构造器: 空构造器,对属性进行初始化
public MyStack() {
head = null;
size = 0;
}
public boolean isEmpty() {
return size == 0;
}
public int size() {
return size;
}
// 入队: 头插法
// 这里可以注意到, 无论是头插法还是尾插法,都要进行(2个)分支判断,1.为空(head==null)直接赋值 2.为不空(head!=null) 要对新增元素进行指针调整
public void push(V value) {
Node<V> cur = new Node<>(value);
if (head == null) {
head = cur;
} else {
cur.next = head;
head = cur;
}
size++;
}
// 出队
public V pop(){
V ans = null;
if(head != null) {
ans = head.value;
head = head.next;
// 一定要注意 对size-- 因为要调整size属性
size--;
}
return ans;
}
public V peek(){
V ans = null;
if (head != null) {
ans = head.value;
}
return ans;
}
}
public static void testQueue() {
MyQueue<Integer> myQueue = new MyQueue<>();
Queue<Integer> test = new LinkedList<>();
int testTime = 5000000;
int maxValue = 200000000;
System.out.println("测试开始!");
for (int i = 0; i < testTime; i++) {
if (myQueue.isEmpty() != test.isEmpty()) {
System.out.println("Oops!");
}
if (myQueue.size() != test.size()) {
System.out.println("Oops!");
}
double decide = Math.random();
if (decide < 0.33) {
int num = (int) (Math.random() * maxValue);
myQueue.offer(num);
test.offer(num);
} else if (decide < 0.66) {
if (!myQueue.isEmpty()) {
int num1 = myQueue.poll();
int num2 = test.poll();
if (num1 != num2) {
System.out.println("Oops!");
}
}
} else {
if (!myQueue.isEmpty()) {
int num1 = myQueue.peek();
int num2 = test.peek();
if (num1 != num2) {
System.out.println("Oops!");
}
}
}
}
if (myQueue.size() != test.size()) {
System.out.println("Oops!");
}
while (!myQueue.isEmpty()) {
int num1 = myQueue.poll();
int num2 = test.poll();
if (num1 != num2) {
System.out.println("Oops!");
}
}
System.out.println("测试结束!");
}
public static void testStack() {
MyStack<Integer> myStack = new MyStack<>();
Stack<Integer> test = new Stack<>();
int testTime = 5000000;
int maxValue = 200000000;
System.out.println("测试开始!");
for (int i = 0; i < testTime; i++) {
if (myStack.isEmpty() != test.isEmpty()) {
System.out.println("Oops!");
}
if (myStack.size() != test.size()) {
System.out.println("Oops!");
}
double decide = Math.random();
if (decide < 0.33) {
int num = (int) (Math.random() * maxValue);
myStack.push(num);
test.push(num);
} else if (decide < 0.66) {
if (!myStack.isEmpty()) {
int num1 = myStack.pop();
int num2 = test.pop();
if (num1 != num2) {
System.out.println("Oops!");
}
}
} else {
if (!myStack.isEmpty()) {
int num1 = myStack.peek();
int num2 = test.peek();
if (num1 != num2) {
System.out.println("Oops!");
}
}
}
}
if (myStack.size() != test.size()) {
System.out.println("Oops!");
}
while (!myStack.isEmpty()) {
int num1 = myStack.pop();
int num2 = test.pop();
if (num1 != num2) {
System.out.println("Oops!");
}
}
System.out.println("测试结束!");
}
public static void main(String[] args) {
testQueue();
testStack();
}
}
双链表实现双向队列
并且,单链表无法实现双向队列。
关键点: 1. 头尾指针。
2. 在插入或弹出节点时,注意将指针设为空,从而令JVM自动释放引用为0 的 对象。
先show我的错误代码: 错误点已经标记: 没有考虑完全。 在出队时 不同于单链表队列。 因为要通过访问指针,并更改为null,来将 节点 通过JVM内部机制进行回收。
package class04;
public class DoubleLinkedListToDeque {
// 定义双链表节点
public static class Node<V> {
// 定义属性 (成员变量)
public V value;
public Node<V> last;
public Node<V> next;
// 定义构造器
public Node(V v) {
value = v;
last = null;
next = null;
}
}
public static class MyDeque<V> {
// 头尾指针,和size
private Node<V> head;
private Node<V> tail;
private int size;
// 构造器
public MyDeque() {
head = null;
tail = null;
size = 0;
}
// 开始定义方法
public boolean isEmpty() {
return size == 0;
}
public int size() {
return size;
}
// 双向队列 pushHead pushTail pollHead pollTail
// poll是队列数据结构实现类的方法,从队首获取元素,同时获取的这个元素将从原队列删除;
// pop是栈结构的实现类的方法,表示返回栈顶的元素,同时该元素从栈中删除,当栈中没有元素时,调用该方法会发生异常
public void pushHead(V value) {
// 接收value 创建泛型 节点对象
Node<V> cur = new Node<>(value);
// 头插法
if (head == null) {
head = cur;
tail = cur;
} else {
// 双链表的头插法
// Step1: 调整双链表指针
cur.next = head;
head.last = cur;
// Step2: 调整头节点head的位置
head = cur;
// 这里忽视了一个事情!!! 我们操作的是 双链表, 有两个指针,next 和 last,全部要进行调整
// 所以进行头插法的时候 要注意,是 进行单链表的头插法,还是双链表的头插法
}
size++;
}
public void pushTail(V value) {
Node<V> cur = new Node<>(value);
// 双链表的尾插法
if (head == null) {
head = cur;
tail = cur;
} else {
// 1. 调整 双链表 指针
tail.next = cur;
cur.last = tail;
// 2. 调整tail指针
tail = cur;
}
size++;
}
public V pollHead() {
V ans = null;
if (head != null) {
ans = head.value;
head = head.next;
// 错误标记!!!!
// 若是只有一个节点时,会出现 空指针异常
head.last = null;
size--;
}
if (head == null) {
tail = null;
}
return ans;
}
public V pollTail() {
V ans = null;
if (head != null) {
ans = tail.value;
tail = tail.last;
// 错误标记!!!!
// 若是只有一个节点时,会出现 空指针异常
tail.next = null;
size--;
}
if (tail == null) {
head = null;
}
return ans;
}
public V peekHead() {
V ans = null;
if (head != null) {
ans = head.value;
}
return ans;
}
public V peekTail() {
V ans = null;
if (tail != null) {
ans = tail.value;
}
return ans;
}
}
}
正确代码:
其实在出队时,只要清晰考虑 队列长度为 0,1,>1的情况即可完成。不同于单链表,只需要考虑 0, >0。 因为单链表只需要通过头尾指针跳转就可令JVM删除脏数据,但是双链表需要通过访问移动后头尾指针所指元素的next,last来删除脏数据。这使得双链表删除时,poll方法,需要多考虑1的情况。
```java
package class04;
import java.util.Deque;
import java.util.LinkedList;
public class Code03_DoubleLinkedListToDeque {
/* 注释部分为左神代码
public static class Node<V> {
public V value;
public Node<V> last;
public Node<V> next;
public Node(V v) {
value = v;
last = null;
next = null;
}
}
public static class MyDeque<V> {
private Node<V> head;
private Node<V> tail;
private int size;
public MyDeque() {
head = null;
tail = null;
size = 0;
}
public boolean isEmpty() {
return size == 0;
}
public int size() {
return size;
}
public void pushHead(V value) {
Node<V> cur = new Node<>(value);
if (head == null) {
head = cur;
tail = cur;
} else {
cur.next = head;
head.last = cur;
head = cur;
}
size++;
}
public void pushTail(V value) {
Node<V> cur = new Node<>(value);
if (head == null) {
head = cur;
tail = cur;
} else {
tail.next = cur;
cur.last = tail;
tail = cur;
}
size++;
}
public V pollHead() {
V ans = null;
if (head == null) {
return ans;
}
size--;
ans = head.value;
if (head == tail) {
head = null;
tail = null;
} else {
head = head.next;
head.last = null;
}
return ans;
}
public V pollTail() {
V ans = null;
if (head == null) {
return ans;
}
size--;
ans = tail.value;
if (head == tail) {
head = null;
tail = null;
} else {
tail = tail.last;
tail.next = null;
}
return ans;
}
public V peekHead() {
V ans = null;
if (head != null) {
ans = head.value;
}
return ans;
}
public V peekTail() {
V ans = null;
if (tail != null) {
ans = tail.value;
}
return ans;
}
}*/
// 定义双链表节点
public static class Node<V> {
// 定义属性 (成员变量)
public V value;
public Node<V> last;
public Node<V> next;
// 定义构造器
public Node(V v) {
value = v;
last = null;
next = null;
}
}
public static class MyDeque<V> {
// 头尾指针,和size
private Node<V> head;
private Node<V> tail;
private int size;
// 构造器
public MyDeque() {
head = null;
tail = null;
size = 0;
}
// 开始定义方法
public boolean isEmpty() {
return size == 0;
}
public int size() {
return size;
}
// 双向队列 pushHead pushTail pollHead pollTail
// poll是队列数据结构实现类的方法,从队首获取元素,同时获取的这个元素将从原队列删除;
// pop是栈结构的实现类的方法,表示返回栈顶的元素,同时该元素从栈中删除,当栈中没有元素时,调用该方法会发生异常
public void pushHead(V value) {
// 接收value 创建泛型 节点对象
Node<V> cur = new Node<>(value);
// 头插法
if (head == null) {
head = cur;
tail = cur;
} else {
// 双链表的头插法
// Step1: 调整双链表指针
cur.next = head;
head.last = cur;
// Step2: 调整头节点head的位置
head = cur;
// 这里忽视了一个事情!!! 我们操作的是 双链表, 有两个指针,next 和 last,全部要进行调整
// 所以进行头插法的时候 要注意,是 进行单链表的头插法,还是双链表的头插法
}
size++;
}
public void pushTail(V value) {
Node<V> cur = new Node<>(value);
// 双链表的尾插法
if (head == null) {
head = cur;
tail = cur;
} else {
// 1. 调整 双链表 指针
tail.next = cur;
cur.last = tail;
// 2. 调整tail指针
tail = cur;
}
size++;
}
/*public V pollHead() {
V ans = null;
if (head != null) {
ans = head.value;
head = head.next;
// 错误标记!!!!
// 若是只有一个节点时,会出现 空指针异常
head.last = null;
size--;
}
if (head == null) {
tail = null;
}
return ans;
}
public V pollTail() {
V ans = null;
if (head != null) {
ans = tail.value;
tail = tail.last;
// 错误标记!!!!
// 若是只有一个节点时,会出现 空指针异常
tail.next = null;
size--;
}
if (tail == null) {
head = null;
}
return ans;
}*/
public V pollHead(){
V ans = null;
if (head == null) {
return ans;
}
ans = head.value;
head = head.next;
// 对只有一个元素 或 多个元素 进行 分支结构
// 目的都是 JVM 释放 脏数据
if(head == null) {
tail = null;
} else {
head.last = null;
}
size--;
return ans;
}
public V pollTail(){
V ans = null;
if (head == null) {
return ans;
}
// 否则 代表队列一定不空,则
ans = tail.value;
tail = tail.last;
if (tail == null) {
head = null;
} else {
tail.next = null;
}
size--;
return ans;
}
public V peekHead() {
V ans = null;
if (head != null) {
ans = head.value;
}
return ans;
}
public V peekTail() {
V ans = null;
if (tail != null) {
ans = tail.value;
}
return ans;
}
}
public static void testDeque() {
MyDeque<Integer> myDeque = new MyDeque<>();
Deque<Integer> test = new LinkedList<>();
int testTime = 5000000;
int maxValue = 200000000;
System.out.println("测试开始!");
for (int i = 0; i < testTime; i++) {
if (myDeque.isEmpty() != test.isEmpty()) {
System.out.println("Oops!");
}
if (myDeque.size() != test.size()) {
System.out.println("Oops!");
}
double decide = Math.random();
if (decide < 0.33) {
int num = (int) (Math.random() * maxValue);
if (Math.random() < 0.5) {
myDeque.pushHead(num);
test.addFirst(num);
} else {
myDeque.pushTail(num);
test.addLast(num);
}
} else if (decide < 0.66) {
if (!myDeque.isEmpty()) {
int num1 = 0;
int num2 = 0;
if (Math.random() < 0.5) {
num1 = myDeque.pollHead();
num2 = test.pollFirst();
} else {
num1 = myDeque.pollTail();
num2 = test.pollLast();
}
if (num1 != num2) {
System.out.println("Oops!");
}
}
} else {
if (!myDeque.isEmpty()) {
int num1 = 0;
int num2 = 0;
if (Math.random() < 0.5) {
num1 = myDeque.peekHead();
num2 = test.peekFirst();
} else {
num1 = myDeque.peekTail();
num2 = test.peekLast();
}
if (num1 != num2) {
System.out.println("Oops!");
}
}
}
}
if (myDeque.size() != test.size()) {
System.out.println("Oops!");
}
while (!myDeque.isEmpty()) {
int num1 = myDeque.pollHead();
int num2 = test.pollFirst();
if (num1 != num2) {
System.out.println("Oops!");
}
}
System.out.println("测试结束!");
}
public static void main(String[] args) {
testDeque();
}
}
## K个节点的组内逆序调整
![在这里插入图片描述](https://img-blog.csdnimg.cn/08b82f392c9e4c539deb713f4c4620ce.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBAd2Fsa2trZXI2NjY=,size_20,color_FFFFFF,t_70,g_se,x_16)
## 链表的顺序合并
## AddTwoNumber