🌠作者:@TheMythWS.
🎆专栏:《集合与数据结构》
🎇座右铭:不走心的努力都是在敷衍自己,让自己所做的选择,熠熠发光。
目录
链表
概念
图解链表
链表的实现
1.创建链表
2.遍历链表
3.查找是否包含关键字key是否在单链表当中
4.获取单链表的长度
5.头插法
6.尾插法
7.任意位置插入(假如第一个数据结点为0下标)
8.删除第一次出现关键字key的结点
9.清空链表
10.删除所有值为key的结点
※链表OJ面试题
LinkedList的模拟实现
头插法:
尾插法:
任意位置插入元素:
删除第一次出现关键字key的结点:
删除所有关键字key的结点:
清空双链表:
LinkedList的使用
什么是LinkedList?
LinkedList的使用
1.LinkedList的构造
2. LinkedList的其他常用方法介绍
3. LinkedList的遍历
ArrayList和LinkedList的区别
链表
概念
是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的,也就是线性表用链式存储结构形成的表。
其特点:用一组任意的存储单元存储线性表的数据元素(这组存储单元可以是连续的,也可以是不连续的)
因此,为了表示每个数据元素ai与其直接后继数据元素ai+1之间的逻辑关系,对数据元素ai来说,除了存储其本身的信息之外,还需要存储一个指示(指针)其直接后继(即直接后继的存储位置)这两部分信息组成数据元素ai的存储映像,称为节点/结点(node),它包括两个域:其中存储数据元素信息的域称为数据域;存储直接后继位置的域称为指针域。指针域存储的信息称作指针或链。n个结点(ai(1≤i≤n)的存储映像)链结成一个链表,即为线性表(a1,a2,...,an)的链式存储结构。如果此链表的每个结点中只包含一个指针域,故又称此链表为线性链表或单链表。
根据链表结点所含指针个数、指针指向和指针链接方式,可以将链表分为单链表、循环链表、双向链表、二叉链表、十字链表、邻接表、邻接多重表等等。
其中单链表、循环链表、双向链表用于实现线性表的链式存储结构,其他形式多用于实现树和图等非线性结构。
类似我们生活中的火车,一节一节相连的。
下面用单链表作为例子:
图解链表
实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:
分为单向和双向,其次是带头和不带头、循环和非循环
所以排列组合有2^3=8种,如下图所示:
单向 不带头 非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如:哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
双向 不带头 非循环链表:在Java的集合框架库中LinkedList底层实现就是 双向 不带头 非循环 链表。
注意:
单向就是一个方向一直走。
双向就是两个方向都走。
链表的实现
以不带头 非循环 单链表作为例子,下面我们先要明白链表和结点是怎么用代码实现的:
1.创建链表
public class MySingleLinkedList {
static class Node {//加static说明创建这个结点的时候不依赖外部对象
public int val;//存储的数据
public Node next;//存储下一个结点的地址
/*
给val构造方法,因为如果不设置(相当于没有这个结点)那么我们不知道结点的next域要存什么地址,
为什么不给next赋值呢? 因为next是一个引用类型,默认是null,这就是结点类
*/
public Node(int val) {
this.val = val;
}
}
public Node head;//代表当前链表的头结点的引用
//创建结点
public void create() {
Node node1 = new Node(10);
Node node2 = new Node(12);
Node node3 = new Node(11);
Node node4 = new Node(9);
node1.next = node2;
node2.next = node3;
node3.next = node4;
head = node1;
}
}
public class Test {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
MySingleLinkedList mySingleLinkedList = new MySingleLinkedList();
mySingleLinkedList.create();
}
}
通过断点调试查看链表是否创建成功:
2.遍历链表
/*
遍历链表
注意点1:
如果是遍历完成整个链表,那么head == null
如果是遍历到链表的最后一个结点(尾结点),那么head.next == null
*/
public void display() {
Node cur = head;//注意点2:让head引用指向头结点,cur来临时指向每个结点,如果不设置的话,那么head = null,连续打印两次链表,只能显示第一次打印的链表
while (cur != null) {//如果判断条件是head.next != null,因为无法进入循环则不能打印最后结点的val
System.out.print(cur.val + " ");
cur = cur.next;
}
System.out.println();
}
public class Test {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
MySingleLinkedList mySingleLinkedList = new MySingleLinkedList();
mySingleLinkedList.create();
mySingleLinkedList.display();
}
}
3.查找是否包含关键字key是否在单链表当中
//查找是否包含关键字key是否在单链表当中
public boolean contains(int key) {
Node cur = head;
while (cur != null) {
if (cur.val == key) {
return true;
}
cur = cur.next;
}
return false;
}
public class Test {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
MySingleLinkedList mySingleLinkedList = new MySingleLinkedList();
mySingleLinkedList.create();
mySingleLinkedList.display();
System.out.println(mySingleLinkedList.contains(9));
}
}
4.获取单链表的长度
//得到单链表的长度 O(N)
public int size() {
int count = 0;
Node cur = head;
while (cur != null) {
count++;
cur = cur.next;
}
return count;
}
5.头插法
//头插法
public void addFirst(int data) {
Node node = new Node(data);//1.插入结点,必须先得有结点
node.next = head;//2.先连
head = node;//3.后断
}
public class Test {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
MySingleLinkedList mySingleLinkedList = new MySingleLinkedList();
mySingleLinkedList.create();
mySingleLinkedList.display();
System.out.println(mySingleLinkedList.contains(9));
System.out.println(mySingleLinkedList.size());
mySingleLinkedList.addFirst(9);
mySingleLinkedList.addFirst(8);
mySingleLinkedList.display();
}
}
6.尾插法
//尾插法 O(N)
public void addLast(int data) {
Node node = new Node(data);
if (head == null) {//该链表是否是第一次插入数据
head = node;
return;
}
Node cur = head;
while (cur.next != null) {//cur指向最后一个结点(尾结点)
cur = cur.next;
}
cur.next = node;
}
public class Test {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
MySingleLinkedList mySingleLinkedList = new MySingleLinkedList();
mySingleLinkedList.create();
mySingleLinkedList.display();
System.out.println(mySingleLinkedList.contains(9));
System.out.println(mySingleLinkedList.size());
System.out.println("《---测试头插法---》");
mySingleLinkedList.addFirst(9);
mySingleLinkedList.addFirst(8);
mySingleLinkedList.display();
System.out.println("《---测试尾插法---》");
mySingleLinkedList.addLast(22);
mySingleLinkedList.addLast(33);
mySingleLinkedList.display();
}
}
7.任意位置插入(假如第一个数据结点为0下标)
//任意位置插入,第一个数据节点为0号下标
public void addIndex(int index, int data) throws LinekdListIndexOutOfException {
checkIndex(index);
if (index == 0) {
addFirst(data);
return;
}
if (index == size()) {
addLast(data);
return;
}
Node cur = findIndexSubOne(index);
Node node = new Node(data);
node.next = cur.next;
cur.next = node;
}
//找到index - 1位置的结点的地址
private Node findIndexSubOne(int index) {
Node cur = head;
int count = 0;
while (count != index - 1) {
cur = cur.next;
count++;
}
return cur;
}
private void checkIndex(int index) throws LinekdListIndexOutOfException {
if (index < 0 || index > size()) {
throw new LinekdListIndexOutOfException("index位置不合法!");
}
}
public class LinekdListIndexOutOfException extends RuntimeException {
public LinekdListIndexOutOfException() {
}
public LinekdListIndexOutOfException(String message) {
super(message);
}
}
public class Test {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
MySingleLinkedList mySingleLinkedList = new MySingleLinkedList();
mySingleLinkedList.create();
mySingleLinkedList.display();
System.out.println(mySingleLinkedList.contains(9));
System.out.println(mySingleLinkedList.size());
System.out.println("《---测试头插法---》");
mySingleLinkedList.addFirst(9);
mySingleLinkedList.addFirst(8);
mySingleLinkedList.display();
System.out.println("《---测试尾插法---》");
mySingleLinkedList.addLast(22);
mySingleLinkedList.addLast(33);
mySingleLinkedList.display();
System.out.println("《---测试插入---》");
mySingleLinkedList.addIndex(3, 11);
mySingleLinkedList.addIndex(0, 7);
mySingleLinkedList.addIndex(10, 44);
try {
mySingleLinkedList.addIndex(12, 44);
} catch (LinekdListIndexOutOfException e) {
e.printStackTrace();
}
mySingleLinkedList.display();
}
}
8.删除第一次出现关键字key的结点
//删除第一次出现关键字为key的结点
public void remove(int key) {
if (head == null) {
return;//一个结点也没有
}
if (head.val == key) {
head = head.next;
return;
}
Node cur = searchPrev(key);//key的前驱结点
if (cur == null) {//没有找到key是要被删除的
return;
}
Node del = cur.next;//要删除的结点
cur.next = del.next;
//cur.next = cur.next.next;
}
//找到关键字key的前一个结点
private Node searchPrev(int key) {
Node cur = head;
while (cur.next != null) {//cur.next == null,它没有下一个结点也就不可能是要找的key结点的前驱,这样就说明了一直没有找到要删除的结点
if (cur.next.val == key) {//如果cur指向的下一个结点的值和key相等,就返回当前cur,当前的cur指向的结点就是key的前一个结点(前驱结点)
return cur;
}
cur = cur.next;
}
return null;//没有要删除的结点
}
public class Test {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
MySingleLinkedList mySingleLinkedList = new MySingleLinkedList();
mySingleLinkedList.create();
mySingleLinkedList.display();
System.out.println(mySingleLinkedList.contains(9));
System.out.println(mySingleLinkedList.size());
System.out.println("《---测试头插法---》");
mySingleLinkedList.addFirst(9);
mySingleLinkedList.addFirst(8);
mySingleLinkedList.display();
System.out.println("《---测试尾插法---》");0
mySingleLinkedList.addLast(22);
mySingleLinkedList.addLast(33);
mySingleLinkedList.display();
System.out.println("《---测试插入---》");
mySingleLinkedList.addIndex(3, 11);
mySingleLinkedList.addIndex(0, 7);
mySingleLinkedList.addIndex(10, 44);
/*try {
mySingleLinkedList.addIndex(12, 44);
} catch (LinekdListIndexOutOfException e) {
e.printStackTrace();
}*/
mySingleLinkedList.display();
System.out.println("《---测试删除---》");
mySingleLinkedList.remove(7);
mySingleLinkedList.remove(8);
mySingleLinkedList.remove(9);
mySingleLinkedList.remove(11);
mySingleLinkedList.display();
}
}
9.清空链表
//清空链表
public void clear() {
head = null;
}
public class Test {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
MySingleLinkedList mySingleLinkedList = new MySingleLinkedList();
mySingleLinkedList.create();
mySingleLinkedList.display();
System.out.println(mySingleLinkedList.contains(9));
System.out.println(mySingleLinkedList.size());
System.out.println("《---测试头插法---》");
mySingleLinkedList.addFirst(9);
mySingleLinkedList.addFirst(8);
mySingleLinkedList.display();
System.out.println("《---测试尾插法---》");
mySingleLinkedList.addLast(22);
mySingleLinkedList.addLast(33);
mySingleLinkedList.display();
System.out.println("《---测试插入---》");
mySingleLinkedList.addIndex(3, 11);
mySingleLinkedList.addIndex(0, 7);
mySingleLinkedList.addIndex(10, 44);
/*try {
mySingleLinkedList.addIndex(12, 44);
} catch (LinekdListIndexOutOfException e) {
e.printStackTrace();
}*/
mySingleLinkedList.display();
System.out.println("《---测试删除---》");
mySingleLinkedList.remove(7);
mySingleLinkedList.remove(8);
mySingleLinkedList.remove(9);
mySingleLinkedList.remove(11);
mySingleLinkedList.display();
System.out.println("《---清空链表---》");
mySingleLinkedList.clear();
mySingleLinkedList.display();
System.out.println("《---更新数据---》");
mySingleLinkedList.addIndex(0 ,11);
mySingleLinkedList.addIndex(1 ,22);
mySingleLinkedList.addIndex(2 ,33);
mySingleLinkedList.addIndex(3 ,22);
mySingleLinkedList.addIndex(4 ,44);
mySingleLinkedList.display();
}
}
10.删除所有值为key的结点
//删除所有值为key的结点
public void removeAllKey(int key) {
if (head == null) {
return;
}
Node prev = head;
Node cur = head.next;
while (cur != null) {
/*while (head.val == key) {
head = head.next;
}*/
if (cur.val == key) {
prev.next = cur.next;
//cur = cur.next;
} else {
prev = cur;
//cur = cur.next;
}
cur = cur.next;
}
if (head.val == key) {//处理头结点也是key的情况
head = head.next;
}
}
public class Test {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
MySingleLinkedList mySingleLinkedList = new MySingleLinkedList();
mySingleLinkedList.create();
mySingleLinkedList.display();
System.out.println(mySingleLinkedList.contains(9));
System.out.println(mySingleLinkedList.size());
System.out.println("《---测试头插法---》");
mySingleLinkedList.addFirst(9);
mySingleLinkedList.addFirst(8);
mySingleLinkedList.display();
System.out.println("《---测试尾插法---》");
mySingleLinkedList.addLast(22);
mySingleLinkedList.addLast(33);
mySingleLinkedList.display();
System.out.println("《---测试插入---》");
mySingleLinkedList.addIndex(3, 11);
mySingleLinkedList.addIndex(0, 7);
mySingleLinkedList.addIndex(10, 44);
/*try {
mySingleLinkedList.addIndex(12, 44);
} catch (LinekdListIndexOutOfException e) {
e.printStackTrace();
}*/
mySingleLinkedList.display();
System.out.println("《---测试删除---》");
mySingleLinkedList.remove(7);
mySingleLinkedList.remove(8);
mySingleLinkedList.remove(9);
mySingleLinkedList.remove(11);
mySingleLinkedList.display();
System.out.println("《---清空链表---》");
mySingleLinkedList.clear();
mySingleLinkedList.display();
System.out.println("《---更新数据---》");
mySingleLinkedList.addIndex(0 ,11);
mySingleLinkedList.addIndex(1 ,22);
mySingleLinkedList.addIndex(2 ,33);
mySingleLinkedList.addIndex(3 ,22);
mySingleLinkedList.addIndex(4 ,44);
mySingleLinkedList.display();
System.out.println("《---删除所有值为key的结点---》");
mySingleLinkedList.removeAllKey(22);
mySingleLinkedList.display();
}
}
总的代码:
public class MySingleLinkedList {
static class Node {//加static说明创建这个结点的时候不依赖外部对象
public int val;//存储的数据
public Node next;//存储下一个结点的地址
/*
给val构造方法,因为如果不设置,那么我们不知道结点的next域要存什么地址,
为什么不给next赋值呢? 因为next是一个引用类型,默认是null,这就是结点类
*/
public Node(int val) {
this.val = val;
}
}
public Node head;//代表当前链表的头结点的引用
//创建结点
public void create() {
Node node1 = new Node(10);
Node node2 = new Node(12);
Node node3 = new Node(11);
Node node4 = new Node(9);
node1.next = node2;
node2.next = node3;
node3.next = node4;
head = node1;
}
/*
遍历链表
注意点1:
如果是遍历完成整个链表,那么head == null
如果是遍历到链表的最后一个结点(尾结点),那么head.next == null
*/
public void display() {
Node cur = head;//注意点2:让head引用指向头结点,cur来临时指向每个结点,如果不设置的话,那么head = null,连续打印两次链表,只能显示第一次打印的链表
while (cur != null) {//如果判断条件是head.next != null,因为无法进入循环则不能打印最后结点的val
System.out.print(cur.val + " ");
cur = cur.next;
}
System.out.println();
}
//查找是否包含关键字key是否在单链表当中
public boolean contains(int key) {
Node cur = head;
while (cur != null) {
if (cur.val == key) {
return true;
}
cur = cur.next;
}
return false;
}
//得到单链表的长度 O(N)
public int size() {
int count = 0;
Node cur = head;
while (cur != null) {
count++;
cur = cur.next;
}
return count;
}
//头插法 O(1)
public void addFirst(int data) {
Node node = new Node(data);//1.插入结点,必须先得有结点
node.next = head;//2.先连
head = node;//3.后断
}
//尾插法 O(N)
public void addLast(int data) {
Node node = new Node(data);
if (head == null) {//该链表是否是第一次插入数据
head = node;
return;
}
Node cur = head;
while (cur.next != null) {//cur指向最后一个结点(尾结点)
cur = cur.next;
}
cur.next = node;
/*node = cur;无意义*/
}
//任意位置插入,第一个数据节点为0号下标
public void addIndex(int index, int data) throws LinekdListIndexOutOfException {
checkIndex(index);
if (index == 0) {
addFirst(data);
return;
}
if (index == size()) {
addLast(data);
return;
}
Node cur = findIndexSubOne(index);
Node node = new Node(data);
node.next = cur.next;
cur.next = node;
}
//找到index - 1位置的结点的地址
private Node findIndexSubOne(int index) {
Node cur = head;
int count = 0;
while (count != index - 1) {
cur = cur.next;
count++;
}
return cur;
}
private void checkIndex(int index) throws LinekdListIndexOutOfException {
if (index < 0 || index > size()) {
throw new LinekdListIndexOutOfException("index位置不合法!");
}
}
//删除第一次出现关键字为key的结点
public void remove(int key) {
if (head == null) {
return;//一个结点也没有
}
if (head.val == key) {
head = head.next;
return;
}
Node cur = searchPrev(key);//key的前驱结点
if (cur == null) {
return;
}
Node del = cur.next;//要删除的结点
cur.next = del.next;
//cur.next = cur.next.next;
}
//找到关键字key的前一个结点
private Node searchPrev(int key) {
Node cur = head;
while (cur.next != null) {//cur.next == null说明一直没有要删除的结点
if (cur.next.val == key) {//如果cur指向的下一个结点的值和key相等,就返回当前cur,当前的cur指向的结点就是key的前一个结点(前驱结点)
return cur;
}
cur = cur.next;
}
return null;//没有要删除的结点
}
//删除所有值为key的结点
public void removeAllKey(int key) {
if (head == null) {
return;
}
Node prev = head;
Node cur = head.next;
while (cur != null) {
/*while (head.val == key) {
head = head.next;
}*/
if (cur.val == key) {
prev.next = cur.next;
//cur = cur.next;
} else {
prev = cur;
//cur = cur.next;
}
cur = cur.next;
}
if (head.val == key) {//处理头结点也是key的情况
head = head.next;
}
}
//清空链表
public void clear() {
head = null;
}
}
public class Test {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
MySingleLinkedList mySingleLinkedList = new MySingleLinkedList();
mySingleLinkedList.create();
mySingleLinkedList.display();
System.out.println(mySingleLinkedList.contains(9));
System.out.println(mySingleLinkedList.size());
System.out.println("《---测试头插法---》");
mySingleLinkedList.addFirst(9);
mySingleLinkedList.addFirst(8);
mySingleLinkedList.display();
System.out.println("《---测试尾插法---》");
mySingleLinkedList.addLast(22);
mySingleLinkedList.addLast(33);
mySingleLinkedList.display();
System.out.println("《---测试插入---》");
mySingleLinkedList.addIndex(3, 11);
mySingleLinkedList.addIndex(0, 7);
mySingleLinkedList.addIndex(10, 44);
/*try {
mySingleLinkedList.addIndex(12, 44);
} catch (LinekdListIndexOutOfException e) {
e.printStackTrace();
}*/
mySingleLinkedList.display();
System.out.println("《---测试删除---》");
mySingleLinkedList.remove(7);
mySingleLinkedList.remove(8);
mySingleLinkedList.remove(9);
mySingleLinkedList.remove(11);
mySingleLinkedList.display();
System.out.println("《---清空链表---》");
mySingleLinkedList.clear();
mySingleLinkedList.display();
System.out.println("《---更新数据---》");
mySingleLinkedList.addIndex(0 ,11);
mySingleLinkedList.addIndex(1 ,22);
mySingleLinkedList.addIndex(2 ,33);
mySingleLinkedList.addIndex(3 ,22);
mySingleLinkedList.addIndex(4 ,44);
mySingleLinkedList.display();
System.out.println("《---删除所有值为key的结点---》");
mySingleLinkedList.removeAllKey(22);
mySingleLinkedList.display();
}
}
※链表OJ面试题
注意:从这儿开始将前面代码里面Node全部替换了ListNode,方便后续刷题改进代码
1. 删除链表中等于给定值 val 的所有节点。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode removeElements(ListNode head, int val) {
if (head == null) {
return null;
}
ListNode prev = head;
ListNode cur = head.next;
while (cur != null) {
/*while (head.val == key) {
head = head.next;
}*/
if (cur.val == val) {
prev.next = cur.next;
//cur = cur.next;
} else {
prev = cur;
//cur = cur.next;
}
cur = cur.next;
}
if (head.val == val) {
head = head.next;
}
return head;
}
}
2. 反转一个单链表。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
if (head == null) {
return null;
}
if (head.next == null) {
return head;
}
ListNode cur = head.next;
//先将头结点置空
head.next = null;
while (cur != null) {
ListNode curNext = cur.next;
//头插法 插入cur
cur.next = head;
head = cur;
cur = curNext;
}
return head;
}
}
3. 给定一个带有头结点 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode middleNode(ListNode head) {
ListNode fast = head;
ListNode slow = head;
while (fast != null && fast.next != null) {//偶数结点和奇数结点, fast走到的位置不一样, 所以都要考虑
fast = fast.next.next;//fast走两步
slow = slow.next;//slow走一步
}
return slow;//此时slow指向的结点就是中间结点
}
}
4.输入一个链表,输出该链表中倒数第k个结点。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode getKthFromEnd(ListNode head, int k) {
// if (k <= 0 || k > size() || head == null) {//判断是否合法
// return null;
// }
//优化条件:去掉k > size()的条件
if (k <= 0 || head == null) {//判断是否合法
return null;
}
ListNode fast = head;
ListNode slow = head;
//第一步:让fast走k-1步
while (k - 1 != 0) {
fast = fast.next;
//优化条件
if (fast == null) {//没走完k-1步之前fast==null,说明k给大了.
return null;
}
k--;
}
// for (k = k - 1; k > 0; k--) {
// fast = fast.next;
// //优化条件
// if (fast == null) {//没走完k-1步之前fast==null,说明k给大了.
// return null;
// }
// }
//第二步 和 第三步
while (fast.next != null) {
fast = fast.next;
slow = slow.next;
}
return slow;
}
}
5. (重点!!!)将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode mergeTwoLists(ListNode head1, ListNode head2) {
ListNode newHead = new ListNode(0);
ListNode tmp = newHead;
while (head1 != null && head2 != null) {
if (head1.val < head2.val) {
tmp.next = head1;
head1 = head1.next;
//tmp = tmp.next;
} else {
tmp.next = head2;
head2 = head2.next;
//tmp = tmp.next;
}
tmp = tmp.next;
}
if (head1 != null) {//说明head2 == null
tmp.next = head1;
}
if (head2 != null) {//说明head1 == null
tmp.next = head2;
}
return newHead.next;
}
}
6.力扣链接 :编写代码,以给定值x为基准将链表分割成两部分,所有小于x的结点排在大于或等于x的结点之前 。
现有一链表的头指针 ListNode* pHead,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针。牛客链接:n链表分割_牛客题霸_牛客网 (nowcoder.com)
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode partition(ListNode head, int x) {
// write code here
// bs : before start, be: before end
// as : after start, ae : after end
ListNode bs = null;//相当于左半部分的头结点, 不能动
ListNode be = null;
ListNode as = null;//相当于右半部分的头结点, 不能动
ListNode ae = null;
ListNode cur = head;
while (cur != null) {
if (cur.val < x) {
if (bs == null) {//左半部分第一次插入
bs = cur;
be = cur;
//cur = cur.next;
} else { //不是第一次插入了
be.next = cur;
be = be.next;
//cur = cur.next;
}
} else {//cur.val >= x
if (as == null) {//右半部分第一次插入
as = cur;
ae = cur;
//cur = cur.next;
} else {//不是第一次插入了
ae.next = cur;
ae = ae.next;
//cur = cur.next;
}
}
cur = cur.next;
}
//此时链表已经遍历完成了, 且分成了两部分, 左半边部分<x, 右半部分>=x
//需要考虑ae是否需要置空???
//有可能不会同时存在小于x 和 大于等于x的数据, 所以需要考虑所有的值都小于x 或者 所有的值都大于等于x的情况.
//注意:下面这个if判断只能判断左半部分不为空, 可以拿到右半部分的数据
// if (bs != null) {//说明左半部分一定有值<x的.
// be.next = as;
// }
//而下面这种更好的写法可以判断左半部分为空和右半部分为空的情况, 从而分别拿到右半部分和左半部分的数据
if (bs == null) {//说明左半部分为空, 只有右半部分的值,即数据都是大于等于x的.
return as;//直接返回右半部分的头结点, 也包含了右半部分为空的情况.如果as == null, 也是返回null.
}
//左半部分不为空
be.next = as;
//右半部分不为空
if (as != null) {
ae.next = null;
}
return bs;
}
}
7. 链表的回文结构。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public boolean isPalindrome(ListNode head) {
if (head == null) {
return false;
}
if (head.next == null) {//一个结点
return true;
}
ListNode fast = head;
ListNode slow = head;
//1.找中间结点
while (fast != null && fast.next != null) {//奇数结点和偶数结点, fast走到的位置不一样, 所以都要考虑
fast = fast.next.next;//fast走两步
slow = slow.next;//slow走一步
}
//此时slow指向的结点就是中间结点
//2.反转
ListNode cur = slow.next;//cur代表当前要反转的结点
while (cur != null) {
ListNode curNext = cur.next;
cur.next = slow;
slow = cur;
cur = curNext;
}
//3.一个从头往后, 一个从后往前遍历, 比较值
while (slow != head) {//直到slow和head相遇 slow == head
if (head.val != slow.val) {
return false;
}
//偶数结点的情况:
if (head.next == slow) {
return true;
}
head = head.next;
slow = slow.next;
}
return true;
}
}
8. 输入两个链表,找出它们的第一个公共结点。
/**
* 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;
}
if (headA == null) {
return null;
}
if (headB == null) {
return null;
}
//1.分别两个链表的长度.
int lenA = 0;
int lenB = 0;
ListNode pl = headA;//假设pl是指向长的链表
ListNode ps = headB;//假设ps是指向短的链表
while (pl != null) {
lenA++;
pl = pl.next;
}
while (ps != null) {
lenB++;
ps = ps.next;
}
//因为上面判断链表的长度, pl 和 ps都指向null, 所以需要将它们指回来.
pl = headA;
ps = headB;
//3.根据len的值 修改指向.
int len = lenA - lenB;
if (len < 0) {
pl = headB;
ps = headA;
len = lenB - lenA;
}
/*
通过上述步骤得到:
1.len一定是一个正数.
2.pl指向的链表一定是最长的, ps指向的链表一定是最短的.
*/
//4.让 pl 走 len 步
while (len != 0) {
pl = pl.next;
len--;
}
while (pl != ps) {
pl = pl.next;
ps = ps.next;
}
//pl == ps
// if (pl == ps && pl == null) {//不需要考虑ps == null, 因为pl已经和ps相等了.
// return null;
// }
//优化:
/*if (pl == null) {//上面while结束之后, 已经是pl == ps的条件了
return null;
}*/
//或者直接注释, 因为返回null, 就说明没有相交.
return pl;
}
}
9. 给定一个链表,判断链表中是否有环。
/**
* 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;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if (fast == slow) {
return true;
}
}
return false;
/*ListNode fast = head;
ListNode slow = head;
while (fast != null && fast.next != null) {//条件1
fast = fast.next.next;
slow = slow.next;
if (fast == slow) {//条件2
break;
}
}
//走到下面if语句有两种情况, 一种是不满足条件1, 另一种是不满足条件2
if (fast ==null || fast.next==null) {
return false;
}
//其余情况: fast == slow 条件2
return true;*/
}
}
10.给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 NULL。
/**
* 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) {
break;
}
}
if (fast == null || fast.next == null) {
return null;
}
//找到相遇点之后, 让slow指向起始点.
slow = head;
//fast和slow同时每次走1步.
while (fast != slow) {
fast = fast.next;
slow = slow.next;
}
return fast;//返回fast和slow都一样的
//优化写法:
/*ListNode fast = head;
ListNode slow = head;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if (fast == slow) {
slow = head;//找到相遇点之后, 让slow指向起始点, 并结束循环.
break;
}
}
if (fast == null || fast.next == null) {
return null;
}
while (fast != slow) {
fast = fast.next;
slow = slow.next;
}
return slow;//返回fast和slow都一样*/
}
}
LinkedList的模拟实现
底层是:双向 不带头 非循环 链表
static class ListNode {
public int val;
public ListNode prev;//前驱
public ListNode next;//后继
public ListNode(int val) {
this.val = val;
}
}
public ListNode head;
public ListNode last;
头插法:
//头插法 O(1)
public void addFirst(int data) {
ListNode node = new ListNode(data);
if (head == null) {
head = node;
last = node;
} else {
node.next = head;
head.prev = node;
head = node;
}
}
尾插法:
//尾插法 O(1)
public void addLast(int data) {
ListNode node = new ListNode(data);
if (head == null) {
head = node;
last = node;
} else {
last.next = node;
node.prev = last;
last = node;
}
}
任意位置插入元素:
//任意位置插入,第一个数据节点为0号下标
public void addIndex(int index, int data) {
if (index < 0 || index > size()) {
throw new LinekdListIndexOutOfException("index位置不合法!");
}
if (index == 0) {
addFirst(data);
return;
}
if (index == size()) {
addLast(data);
return;
}
ListNode node = new ListNode(data);
ListNode cur = findIndex(index);
node.next = cur;
cur.prev.next = node;
node.prev = cur.prev;
cur.prev = node;
}
private ListNode findIndex(int index) {
ListNode cur = head;
while (index != 0) {
cur = cur.next;
index--;
}
return cur;
}
删除第一次出现关键字key的结点:
//删除第一次出现关键字为key的结点
public void remove(int key) {
ListNode cur = head;
while (cur != null) {
if (cur.val == key) {
//删除头结点
if (cur == head) {
head = head.next;
//只有一个结点
if (head != null) {
head.prev = null;
}
} else {
//删除中间或者尾巴结点
cur.prev.next = cur.next;
if (cur.next != null) {
//删除中间结点
cur.next.prev = cur.prev;
} else {
//删除尾巴结点
last = last.prev;
}
}
return;
}
cur = cur.next;
}
}
删除所有关键字key的结点:
//删除所有值为key的节点
public void removeAllKey(int key) {
ListNode cur = head;
while (cur != null) {
if (cur.val == key) {
//删除头结点
if (cur == head) {
head = head.next;
//只有一个结点
if (head != null) {
head.prev = null;
}
} else {
//删除中间或者尾巴结点
cur.prev.next = cur.next;
if (cur.next != null) {
//删除中间结点
cur.next.prev = cur.prev;
} else {
//删除尾巴结点
last = last.prev;
}
}
//return; 去掉return这样就可以接着删
}
cur = cur.next;
}
}
清空双链表:
//清空双链表
public void clear() {
ListNode cur = head;
while (cur != null) {
ListNode curNext = cur.next;
cur.prev = null;
cur.next = null;
cur = curNext;
}
head = null;
last = null;
}
LinkedList的使用
什么是LinkedList?
LinkedList 的官方文档
LinkedList的底层是双向链表结构(链表后面介绍),由于链表没有将元素存储在连续的空间中,元素存储在单独的节点中,然后通过引用将节点连接起来了,因此在在任意位置插入或者删除元素时,不需要搬移元素,效率比较高。
在集合框架中,LinkedList也实现了List接口,具体如下:
【说明】
- LinkedList实现了List接口
- LinkedList的底层使用了双向链表
- LinkedList没有实现RandomAccess接口,因此LinkedList不支持随机访问
- LinkedList的任意位置插入和删除元素时效率比较高,时间复杂度为O(1)
- LinkedList比较适合任意位置插入的场景
LinkedList的使用
1.LinkedList的构造
2. LinkedList的其他常用方法介绍
public static void main(String[] args) {
LinkedList<Integer> list = new LinkedList<>();
list.add(1); // add(elem): 表示尾插
list.add(2);
list.add(3);
list.add(4);
list.add(5);
list.add(6);
list.add(7);
System.out.println(list.size());
System.out.println(list);
// 在起始位置插入0
list.add(0, 0); // add(index, elem): 在index位置插入元素elem
System.out.println(list);
list.remove(); // remove(): 删除第一个元素,内部调用的是removeFirst()
list.removeFirst(); // removeFirst(): 删除第一个元素
list.removeLast(); // removeLast(): 删除最后元素
list.remove(1); // remove(index): 删除index位置的元素
System.out.println(list);
// contains(elem): 检测elem元素是否存在,如果存在返回true,否则返回false
if (!list.contains(1)) {
list.add(0, 1);
}
list.add(1);
System.out.println(list);
System.out.println(list.indexOf(1)); // indexOf(elem): 从前往后找到第一个elem的位置
System.out.println(list.lastIndexOf(1)); // lastIndexOf(elem): 从后往前找第一个1的位置
int elem = list.get(0); // get(index): 获取指定位置元素
list.set(0, 100); // set(index, elem): 将index位置的元素设置为elem
System.out.println(list);
// subList(from, to): 用list中[from, to)之间的元素构造一个新的LinkedList返回
List<Integer> copy = list.subList(0, 3);
System.out.println(list);
System.out.println(copy);
list.clear(); // 将list中元素清空
System.out.println(list.size());
}
3. LinkedList的遍历
public static void main(String[] args) {
LinkedList<Integer> list = new LinkedList<>();
list.add(1); // add(elem): 表示尾插
list.add(2);
list.add(3);
list.add(4);
list.add(5);
list.add(6);
list.add(7);
System.out.println(list.size());
// foreach遍历
for (Integer e : list) {
System.out.print(e + " ");
}
System.out.println();
// 使用迭代器遍历---正向遍历
ListIterator<Integer> it = list.listIterator();
while (it.hasNext()) {
System.out.print(it.next() + " ");
}
System.out.println();
// 使用反向迭代器---反向遍历
ListIterator<Integer> rit = list.listIterator(list.size());
while (rit.hasPrevious()) {
System.out.print(rit.previous() + " ");
}
System.out.println();
}