🌠作者:@TheMythWS.
🎇座右铭:不走心的努力都是在敷衍自己,让自己所做的选择,熠熠发光。
目录
链表
概念
是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的,也就是线性表用链式存储结构形成的表。
其特点:用一组任意的存储单元存储线性表的数据元素(这组存储单元可以是连续的,也可以是不连续的)
因此,为了表示每个数据元素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) {
checkIndex(index);
if (index == 0) {
addFirst(data);
return;
}
if (index == size()) {
addLast(data);
return;
}
Node cur = findIndexSubOne2(index);
Node node= new Node(data);
node.next = cur.next;
cur.next = node;
}
/**
* 找到 index-1位置的节点的地址
*
* @param index
* @return
*/
private Node findIndexSubOne(int index) {
Node cur = head;
int count = 0;
while (count != index - 1) {
cur = cur.next;
count++;
}
return cur;
}
private Node findIndexSubOne2(int index) {
Node cur = head;
while (index - 1 != 0) {
cur = cur.next;
index--;
}
return cur;
}
private void checkIndex(int index) {
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,方便后续刷题改进代码
/**
* 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 head;//直接返回null也可以
}
if (head.next == null) {
return head;
}
ListNode cur = head.next;//用cur来遍历每个结点
head.next = null;//先将头结点置空
while (cur != null) {
ListNode curNext = cur.next;//指向cur的下一个要遍历的结点,这个结点必须放在循环内要每次更新curNext的值,因为curNext也要往后遍历,如果放在循环外,每次cur = curNext的时候,curNext的值还是最开始的值没有更新
//头插法 插入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指向的结点就是中间结点
}
}
建议解法一!!!
/**
* 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; }
* }
*/
// 1.倒数第K个结点1.先让fast走 k-1步
// 2.fast走完之后和slow开始一步一步的走
// 3.当fast.next == null时候, slow所指的就是倒数第K个结点
class Solution {
public ListNode trainingPlan(ListNode head, int cnt) {
// 判断编号合法性
// if (cnt <= 0 || cnt > size() || head == null) {
// return null;
// }
//优化了 cnt > size() 计算链表的长度(size())会增加额外的时间复杂度O(n),导致算法效率降低。
if (cnt <= 0 || head == null) {
return null;
}
ListNode fast = head;
ListNode slow = head;
// 1.让 fast 走 cnt - 1 步
while (cnt - 1 != 0) {
fast = fast.next;
cnt--;
if (fast == null) {// 没走完 cnt - 1 步之前fast == null,说明 cnt 给大了
return null;
}
//cnt--;//放这里也行
}
// for (cnt = cnt - 1; cnt > 0; cnt--) {//这里for可以,注意区别解法二
// fast = fast.next;
// // 优化条件
// if (fast == null) {// 没走完 cnt - 1 步之前fast == null,说明 cnt 给大了
// return null;
// }
// }
// 2. 和 3.
while (fast.next != null) {
fast = fast.next;
slow = slow.next;
}
return slow;
}
}
/**
* 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; }
* }
*/
// 1.倒数第K个结点1.先让fast走 k 步
// 2.fast走完之后和slow开始一步一步的走
// 3.当fast == null时候, slow所指的就是倒数第K个结点
class Solution {
public ListNode trainingPlan(ListNode head, int cnt) {
// 判断编号合法性
// if (cnt <= 0 || cnt > size() || head == null) {
// return null;
// }
//优化了 cnt > size() 计算链表的长度(size())会增加额外的时间复杂度O(n),导致算法效率降低。
if (cnt <= 0 || head == null) {
return null;
}
ListNode fast = head;
ListNode slow = head;
// 1.让 fast 走 cnt 步
while (cnt != 0) {
fast = fast.next;
cnt--;
if (fast == null && cnt > 0) {// 如果 fast 提前到达 null 并且 cnt 还没减到 0,说明 cnt 超过链表长度
return null;
}
}
// for (cnt = cnt; cnt > 0; cnt--) { !!!这里不能用for,因为cnt--是最后执行!!!
// fast = fast.next;
// // 优化条件
// if (fast == null && cnt > 0) {// 如果 fast 提前到达 null 并且 cnt 还没减到 0,说明 cnt 超过链表长度
// return null;
// }
// }
// 2. 和 3.
while (fast != 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;
}
}
/**
* 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;//假设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;
}
}
/**
* 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;
}
//说明不是第一次插入
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;
}
//说明不是第一次插入
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();
}