目录
1 线性表
数据结构: 描述和组织数据的方式,即数据 + 结构。数据结构是一门逻辑非常严谨的学科。
为什么数据结构那么多?
答:主要是因为描述和组织数据的方式不一样。
怎么学好数据结构?
答:1.画图,因为数据结构是很抽象的;2.多写代码。
线性表(linear list): 是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串等。树、图等都不是线性表。线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。可借助下图进行理解:
2 顺序表
2.1 概念及结构
顺序表: 底层实际上是一个数组。顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储,内存是连续的。在数组上完成数据的增删查改。Java中有自带的顺序表函数,它叫做ArrayList。上图所举例的线性表其实就是一个顺序表,我们也可以借助上图对顺序表进行一定的理解。
顺序表一般可以分为:
1. 静态顺序表:使用定长数组存储。
静态顺序表适用于确定知道需要存多少数据的场景。
2. 动态顺序表:使用动态开辟的数组存储。
静态顺序表的定长数组导致N定大了,空间开多了浪费,开少了不够用。相比之下动态顺序表更灵活, 根据需要动态的分配空间大小.
2.2 接口实现
我们自己来实现一个动态顺序表,以下是需要支持的接口,具体代码示例:
import java.util.Arrays;
public class MyArrayList {
/*public int[] elem = new int[10];
public int usedSize = 0;*/
private int[] elem;
private int usedSize;
public MyArrayList(){
this.elem = new int[5];
}
public MyArrayList(int capacity){
this.elem = new int[capacity];
}
// 打印顺序表
public void display() {
for (int i = 0; i < this.usedSize; i++) {
System.out.print(this.elem[i] + " ");
}
System.out.println();
}
//判断当前顺序表是否是满的
public boolean isFull(){
if(this.usedSize == this.elem.length){
return true;
}
return false;
}
//扩容
public void resize(){
this.elem = Arrays.copyOf(this.elem,2*this.elem.length);
}
// 在 pos 位置新增元素
/*
* 1、判断此顺序表是否为满的full
* 2、判断pos位置的合法性
* 3、插入数据的时候,这个位置前一定要有数据,否则不能插入
* */
public void add(int pos, int data) {
//顺序表是否满
if(isFull()){
//System.out.println("顺序表为满!");
resize();
return;
}
//pos是否合法
if(pos < 0 || pos > this.usedSize){
System.out.println("pos位置不合法!");
return;
}
//移动元素
for (int i = this.usedSize - 1; i >= pos ; i--) {
this.elem[i+1] = this.elem[i];
}
this.elem[pos] = data;
this.usedSize++;
}
//默认插入到数组的最后
public void add2(int data){
if(isFull()){
// System.out.println("顺序表为满!");
resize();
// return;
}
this.elem[this.usedSize] = data;
this.usedSize++;
}
// 判定是否包含某个元素
public boolean contains(int toFind) {
for (int i = 0; i < this.usedSize ; i++) {
if(this.elem[i] == toFind){
return true;
}
}
return false; }
// 查找某个元素对应的位置
public int search(int toFind) {
for (int i = 0; i < this.usedSize ; i++) {
if(this.elem[i] == toFind){
return i;
}
}
return -1; }
// 获取 pos 位置的元素
public int getPos(int pos) {
if(pos < 0 || pos > this.usedSize){
return -1;
}
return -1; }
// 给 pos 位置的元素设(修改)为 value
public void setPos(int pos, int value) {
if(pos < 0 || pos > this.usedSize){
return;
}
this.elem[pos] = value;
}
//删除第一次出现的关键字key
/*
* 1、查找是否有2 index
* 2、i = index i < usedSize - 1
* 3、this.useSize--
* */
public void remove(int key) {
int index = search(key);
if(index == -1){
System.out.println("没有找到该元素");
return;
}
for (int i = index; i <this.usedSize - 1 ; i++) {
this.elem[i] = this.elem[i+1];
}
this.usedSize--;
}
// 获取顺序表长度
public int size() {
return this.usedSize; }
// 清空顺序表
public void clear() {
this.usedSize = 0;
}
}
在上面的代码基础上,调用测试部分代码,具体代码示例如下:
public class TestDemo2 {
public static void main(String[] args) {
MyArrayList myArrayList = new MyArrayList();
MyArrayList myArrayList2 = new MyArrayList(20);
myArrayList.add(0,9);
myArrayList.add(1,19);
myArrayList.add(2,29);
myArrayList.add(3,39);
myArrayList.display();
myArrayList.add2(8888);
myArrayList.add2(1111);
myArrayList.add2(4444);
myArrayList.display();
}
}
2.3 顺序表的优缺点
优点: 如果是通过下标直接取数据,时间复杂度可以达到O(1)。
缺点:
- 在插入元素的时候,顺序表中间/头部的插入删除,时间复杂度最坏是O(n)。
- 不能做到随用随取。 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。
- 增容需要申请新空间,拷贝数据,释放旧空间,会有不小的消耗。
3 链表
3.1 概念及结构
链表: 是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的 ,内存是非连续的。下图就是一个简单的链表,具体可借助下图进行理解:
图中每个节点就是一个节点对象。data:代表数值域;next:代表引用,用来存放下一个节点的地址。第一个节点是头节点,最后一个节点是尾节点。头结点的作用是用来表示头部的。
实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:
- 单向、双向
- 带头、不带头
- 循环、非循环
单链表: 只是沿着某一方向走,这种链表叫做单链表。
单向不带头非循环链表(研究重点): 如下图所示就是一个单向不带头非循环链表。
单向带头结点的非循环链表: 如下图所示就是一个单向带头结点的非循环链表。
单向不带头循环链表: 如下图所示就是一个单向不带头循环链表。
单向带头循环链表: 如下图所示就是一个单向带头循环链表。
注意区分带傀儡节点的链表和不带傀儡节点的链表区别,带傀儡节点的链表也就是设置了一个假的头,如下图所示,我们进行头插法时将从第二个那个假的头后面进行插入;而不带傀儡节点的链表就是平时写的链表,我们进行头插法时将从头进行插入即可。带傀儡节点的链表如下图所示:
双向链表: 如下图所示就是一个双向链表。
3.2 链表的实现
我们这里只实现我们重点研究的链表,也就是以下这种:
3.2.1 单向不带头非循环链表的实现
具体代码示例如下:
class Node{
public int val;
public Node next;
public Node(){
}
public Node(int val){
this.val = val;
}
}
public class MyLinkedList {
public Node head;//表示当前链表的头,默认是null
/*
*单链表的创建和遍历过程
* */
public void createLinked(){
this.head = new Node(12);
Node node2 = new Node(22);
Node node3 = new Node(32);
Node node4 = new Node(42);
this.head.next = node2;
node2.next = node3;
node3.next = node4;
}
/*
* 通过遍历,找到链表的最后一个节点
* */
public Node findLastNode(){
if(this.head == null){
System.out.println("head == null");
return null;
}
Node cur = this.head;
while(cur.next != null){
cur = cur.next;
}
return cur;
}
/*
*通过遍历,找到链表的倒数第二个节点
* */
public Node findLastTwoNode(){
if(this.head == null){
System.out.println("此链表什么都没有!");
return null;
}
if(this.head.next == null){
System.out.println("此链表只有一个节点!");
return null;
}
Node cur = this.head;
while(cur.next.next != null){
cur = cur.next;
}
return cur;
}
/*
*通过遍历,找到链表的第n个节点(链表长度>=n)
* */
public Node findN(int n){
if(this.head == null){
System.out.println("此单链表为空!");
return null;
}
if(n <= 0){
System.out.println("n太小了,不合法");
return null;
}
if(n > size()){
System.out.println("n太大了,已经大于链表的长度了!");
return null;
}
int count = 1;
Node cur = this.head;
while(count != n){
cur = cur.next;
count++;
}
return cur;
}
//头插法
public void addFirst(int data) {
/*
* 1、首先你得有个节点
* 2、判断链表是不是空的
* 3、插入
* */
/* Node node = new Node(data);
//在任何情况下,在插入的时候,先绑定后面
if(this.head == null){
this.head = node;
}else{
node.next = this.head;
this.head = node;
}*/
//优化以后的代码如下所示:
Node node = new Node(data);
node.next = this.head;
this.head = node;
}
//尾插法
public void addLast(int data){
/*
* 1、首先你得有个节点
* 2、cur 找尾巴
* 3、插入
* */
Node node = new Node(data);
if(this.head == null){
this.head = node;
}else{
Node cur = this.head;
while(cur.next != null){
cur = cur.next;
}
cur.next = node;
}
}
/*
* 该函数是找到index-1位置的节点的引用
* */
public Node moveIndex(int index){
Node cur = this.head;
int count = 0;
while(count != index-1){
cur = cur.next;
count++;
}
return cur;
}
//任意位置插入,第一个数据节点为0号下标
public void addIndex(int index,int data) {
if(index < 0 || index > size()){
System.out.println("index不合法!");
return;
}
//头插法
if(index == 0){
addFirst(data);
return;
}
//尾插法
if(index == size()){
addLast(data);
return;
}
Node cur = moveIndex(index);
//cur保存的是index-1位置的节点的引用
Node node = new Node(data);
node.next = cur.next;
cur.next = node;
}
//查找是否包含关键字key是否在单链表当中
public boolean contains(int key) {
Node cur = this.head;
while(cur != null){
if(cur.val == key){
return true;
}
cur = cur.next;
}
return false;
}
/*
* 找到关键key的前驱,如果有返回前驱节点的引用
* 如果没有返回null
* */
public Node searchPrev(int key){
Node cur = this.head;
while(cur.next != null) {
if (cur.next.val == key) {
return cur;
}
cur = cur.next;
}
return null;//代表没有要删除的key值的前驱
}
//删除第一次出现关键字为key的节点
public void remove(int key) {
if(this.head == null){
return;
}
if(this.head.val == key){
this.head = this.head.next;
return;
}
//正常的进行删除
Node cur = searchPrev(key);
if(cur == null){
System.out.println("删除失败,因为没有key的前驱");
}else{
Node del = cur.next;
cur.next = del.next;
}
}
//删除所有值为key的节点
public void removeAllKey(int key){
if(this.head == null){
return;
}
Node prev = this.head;
Node cur = prev.next;
while(cur != null){
if(cur.val == key){
prev.next = cur.next;
}else{
prev = cur;
}
cur = cur.next;
}
if(this.head.val == key){
this.head = this.head.next;
}
}
//得到单链表的长度
public int size() {
Node cur = this.head;
int count = 0;
while(cur != null){
count++;
cur = cur.next;
}
return count;
}
//打印链表
public void display() {
Node cur = this.head;
while(cur != null){
System.out.print(cur.val + " ");
cur = cur.next;
}
System.out.println();
}
/*
* 从指定的位置newHead
* 开始进行打印
* */
public void display(Node newHead) {
Node cur = newHead;
while(cur != null){
System.out.print(cur.val + " ");
cur = cur.next;
}
System.out.println();
}
//释放链表
public void clear() {
this.head = null;
}
//反转一个单链表
//方法1:
public Node reverseList() {
Node cur = this.head;
Node prev = null;//prev代表节点的先驱
Node newHead = null;
while (cur != null) {
Node curNext = cur.next;//curNext代表需要翻转的下一个节点
if(curNext == null){
newHead = cur;
}
cur.next = prev;
prev = cur;
cur = curNext;
}
return newHead;
}
//方法2:用头插法反转一个单链表
public Node reverseList1() {
Node cur = this.head;
Node prev = null;//prev代表节点的先驱
Node newHead = null;
while (cur != null) {
Node curNext = cur.next;//curNext代表需要翻转的下一个节点
cur.next = prev;
prev = cur;
cur = curNext;
}
return prev;
}
//给定一个带有头结点 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点
//方法1:通过求长度求解此题
public Node middleNode1(){
int len = size()/2;
Node cur = this.head;
int count = 0;
while(count != len){
cur = cur.next;
count++;
}
return cur;
}
//方法2:不能通过求长度求解此题
public Node middleNode() {
Node fast = this.head;
Node slow = this.head;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
}
return slow;
}
public static void main1(String[] args) {
Node node = new Node();
System.out.println(node.val);
System.out.println(node.next);
}
}
在上面的代码基础上,调用测试部分代码,具体代码示例如下:
public class TestDemo3 {
public static void main(String[] args) {
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.addLast(1);
myLinkedList.addLast(2);
myLinkedList.addLast(3);
myLinkedList.addLast(4);
myLinkedList.addLast(5);
myLinkedList.display();
Node ret = myLinkedList.reverseList1();
myLinkedList.display(ret);
}
public static void main6(String[] args) {
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.addLast(1);
myLinkedList.addLast(2);
myLinkedList.addLast(2);
myLinkedList.addLast(4);
myLinkedList.addLast(5);
myLinkedList.display();
myLinkedList.clear();
System.out.println("===========================");
}
public static void main5(String[] args) {
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.addLast(1);
myLinkedList.addLast(2);
myLinkedList.addLast(2);
myLinkedList.addLast(4);
myLinkedList.addLast(5);
myLinkedList.display();
System.out.println("===========================");
myLinkedList.removeAllKey(2);
myLinkedList.display();
}
public static void main4(String[] args) {
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.addLast(1);
myLinkedList.addLast(2);
myLinkedList.addLast(3);
myLinkedList.addLast(4);
myLinkedList.addLast(5);
myLinkedList.display();
System.out.println("===========================");
myLinkedList.remove(3);
myLinkedList.display();
}
public static void main3(String[] args) {
MyLinkedList myLinkedList = new MyLinkedList();
/* //这个函数之后,我们拿到了当前链表的头节点
myLinkedList.createLinked();
myLinkedList.display();
System.out.println("=============================");*/
myLinkedList.addFirst(19);
myLinkedList.addFirst(29);
myLinkedList.addFirst(39);
myLinkedList.display();//39 29 19
myLinkedList.addLast(1);
myLinkedList.addLast(2);
myLinkedList.addLast(3);
myLinkedList.addLast(4);
myLinkedList.display();//39 29 19 1 2 3 4
myLinkedList.addIndex(0,110);
myLinkedList.display();//110 39 29 19 1 2 3 4
myLinkedList.addIndex(2,120);
myLinkedList.display();//110 39 120 29 19 1 2 3 4
myLinkedList.addIndex(9,119);
myLinkedList.display();//110 39 120 29 19 1 2 3 4 119
myLinkedList.addIndex(-9,119);
}
public static void main2(String[] args) {
MyLinkedList myLinkedList = new MyLinkedList();
//这个函数之后,我们拿到了当前链表的头节点
myLinkedList.createLinked();
myLinkedList.display();
System.out.println("=============================");
int n = 4;
Node ret = myLinkedList.findN(n);
System.out.println("第"+ n +"个节点是:"+ret.val);
System.out.println("=============================");
System.out.println(myLinkedList.size());
System.out.println("=============================");
System.out.println(myLinkedList.contains(12));
}
public static void main1(String[] args) {
MyLinkedList myLinkedList = new MyLinkedList();
//这个函数之后,我们拿到了当前链表的头节点
myLinkedList.createLinked();
myLinkedList.display();
System.out.println("=============================");
//处理异常的方式
try{
//放的是可能出现异常的代码
Node ret = myLinkedList.findLastNode();
System.out.println(ret.val);
}catch (NullPointerException e){
//catch里面放的是出现异常的异常名字,后面再加一个e
}
/*Node ret = myLinkedList.findLastNode();
System.out.println(ret.val);*/
System.out.println("=============================");
/*ret = myLinkedList.findLastTwoNode();
System.out.println(ret.val);*/
//会发生异常的原因是,我们在进行测试时把此链表的创建过程给屏蔽了,也就是此时这是一个空链表,所以此时在ret.val处才会出现空指针异常
System.out.println("虽然发生了异常,但是我还是想打印一下这句话");
}
}
3.2.2 双向链表的实现
具体代码示例如下:
//双向链表
class ListNode{
public int val;
public ListNode next;
public ListNode prev;
//快捷键alt + insert 生成构造方法Constructor
public ListNode(int val) {
this.val = val;
}
public ListNode getNext() {
return next;
}
public int getVal() {
return val;
}
public ListNode getPrev() {
return prev;
}
public void setVal(int val) {
this.val = val;
}
public void setNext(ListNode next) {
this.next = next;
}
public void setPrev(ListNode prev) {
this.prev = prev;
}
}
public class DoubleLinkedList {
private ListNode head;//头
private ListNode last;//尾
//头插法
public void addFirst(int data){
ListNode node = new ListNode(data);
if(this.head == null){
this.head = node;
this.last = node;
}else{
node.setNext(this.head); //node.next = this.head;
this.head.setPrev(node);//head.prev = node;
this.head = node;
}
}
//尾插法
public void addLast(int data) {
ListNode node = new ListNode(data);
if(this.head == null){
this.head = node;
this.last = node;
}else{
//last.next = node;
this.last.setNext(node);
//node.prev = last;
node.setPrev(this.last);
this.last = node;
}
}
//任意位置插入,第一个数据节点为0号下标
public void addIndex(int index,int data) {
if(index <0 || index > size()){
return;
}
if(index == 0){
addFirst(data);
return;
}
if(index == size()){
addLast(data);
return;
}
ListNode cur = this.head;
while(index != 0){
//cur = cur.next;
cur = cur.getNext();
index--;
}
ListNode node = new ListNode(data);
//node.next = cur;
node.setNext(cur);
//node.prev = cur.prev;
node.setPrev(cur.getPrev());
//node.prev.next = node;
node.getPrev().setNext(node);
//cur.prev = node;
cur.setPrev(node);
}
//查找是否包含关键字key是否在单链表当中
public boolean contains(int key) {
ListNode cur = this.head;
while(cur != null){
if(cur.getVal() == key){
return true;
}
cur = cur.getNext();
}
return false;
}
public ListNode findNode(int key){
ListNode cur = this.head;
while(cur != null){
if(cur.getVal() == key){
return cur;
}
cur = cur.getNext();
}
return null;
}
//删除第一次出现关键字为key的节点
public void remove(int key) {
ListNode cur = this.findNode(key);
if(cur == null){
return;
}
if(cur == this.head){
this.head = this.head.getNext();
//this.head.prev = null;
this.head.setPrev(null);
return;
}
if(cur == this.last){
cur.getPrev().setNext(null);
this.last = this.last.getPrev();
return;
}
cur.getPrev().setNext(cur.getNext());
cur.getNext().setPrev(cur.getPrev() );
}
public void remove2(int key) {
ListNode cur = this.head;
while(cur != null){
if(cur.val == key){
if(cur == this.head){
//头节点
this.head = this.head.next;
this.head.prev = null;
}else{
cur.prev.next = cur.next;
if(cur.next != null){
cur.next.prev = cur.prev;
}else{
//cur.next == null;
this.last=this.last.prev;
}
}
return;
}else{
cur = cur.next;
}
}
}
public void remove3(int key) {
ListNode cur = this.head;
while(cur != null){
if(cur.val == key){
if(cur == this.head){
this.head = this.head.next;
this.head.prev = null;
}else if(cur == this.last){
cur.prev.next = null;
this.last=this.last.prev;
}else{
cur.prev.next = cur.next;
cur.next.prev = cur.prev;
}
return;
}
cur = cur.next;
}
}
//删除所有值为key的节点
public void removeAllKey(int key) {
ListNode cur = this.head;
while(cur != null){
if(cur.val == key){
if(cur == this.head){
//头节点
this.head = this.head.next;
this.head.prev = null;
}else{
cur.prev.next = cur.next;
if(cur.next != null){
cur.next.prev = cur.prev;
}else{
//cur.next == null;
this.last=this.last.prev;
}
}
// return;
}
cur = cur.next;
}
}
public void removeAllKey2(int key) {
ListNode cur = this.head;
while(cur != null){
if(cur.val == key){
if(cur == this.head){
this.head = this.head.next;
this.head.prev = null;
}else if(cur == this.last){
cur.prev.next = null;
this.last=this.last.prev;
}else{
cur.prev.next = cur.next;
cur.next.prev = cur.prev;
}
//return;
}
cur = cur.next;
}
}
//得到单链表的长度
public int size() {
ListNode cur = this.head;
int count = 0;
while(cur != null){
count++;
cur = cur.getNext();
}
return count;
}
//打印
public void display() {
ListNode cur = this.head;
while(cur != null){
//cur = cur.next;
System.out.print(cur.getVal()+" ");
cur = cur.getNext();
}
System.out.println();
}
public void clear() {
this.head = null;
this.last = null;
}
public void clear2(){
ListNode cur = this.head;
while(cur != null){
ListNode curNext = cur.next;
cur.next = null;
cur.prev = null;
cur = curNext;
}
this.last = null;
this.head = null;
}
}
在上面的代码基础上,调用测试部分代码,具体代码示例如下:
public class TestDemo3 {
public static void main(String[] args) {
DoubleLinkedList doubleLinkedList = new DoubleLinkedList();
doubleLinkedList.addFirst(1);
doubleLinkedList.addFirst(1);
doubleLinkedList.addFirst(1);
doubleLinkedList.addFirst(4);
doubleLinkedList.addLast(1);
doubleLinkedList.addFirst(1);
doubleLinkedList.display();
doubleLinkedList.addIndex(0,99);
doubleLinkedList.display();
doubleLinkedList.remove(2);
doubleLinkedList.display();
doubleLinkedList.remove2(4);
doubleLinkedList.display();
doubleLinkedList.removeAllKey(1);
doubleLinkedList.display();
doubleLinkedList.removeAllKey2(1);
doubleLinkedList.display();
}
3.3 链表习题
例1:删除链表中等于给定值 val 的所有节点。
OJ链接:https://leetcode.cn/problems/remove-linked-list-elements/submissions/381415797
例2:反转一个单链表。
OJ链接:https://leetcode.cn/problems/reverse-linked-list/submissions/381429359/
例3: 给定一个带有头结点 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。
OJ链接:https://leetcode.cn/problems/middle-of-the-linked-list/submissions/381439422/
例4: 输入一个链表,输出该链表中倒数第k个结点。
OJ链接:https://www.nowcoder.com/practice/529d3ae5a407492994ad2a246518148a?tpId=13&&tqId=11167&rp=2&ru=/activity/oj&qru=/ta/coding-interviews/question-ranking
例5: 将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
OJ链接:https://leetcode.cn/problems/merge-two-sorted-lists/submissions/381457964/
例6: 编写代码,以给定值x为基准将链表分割成两部分,所有小于x的结点排在大于或等于x的结点之前 。
OJ链接:https://www.nowcoder.com/practice/0e27e0b064de4eacac178676ef9c9d70?tpId=8&&tqId=11004&rp=2&ru=/activity/oj&qru=/ta/cracking-the-coding-interview/question-ranking
例7:在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。
OJ链接:https://www.nowcoder.com/practice/fc533c45b73a41b0b44ccba763f866ef?tpId=13&&tqId=11209&rp=1&ru=/activity/oj&qru=/ta/coding-interviews/question-ranking
例8: 链表的回文结构。
回文: 如果一组数据正着读和反着读是一样的,则它就是回文结构,如:1 2 3 2 1,这就是一个回文结构。
OJ链接:https://www.nowcoder.com/practice/d281619e4b3e4a60a2cc66ea32855bfa?tpId=49&&tqId=29370&rp=1&ru=/activity/oj&qru=/ta/2016test/question-ranking
例9:输入两个链表,找出它们的第一个公共结点。
OJ链接:https://leetcode.cn/problems/intersection-of-two-linked-lists/submissions/382586196/
例10: 给定一个链表,判断链表中是否有环。
OJ链接:https://leetcode.cn/problems/linked-list-cycle/submissions/383855755/
例11:给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
解题思路如下图所示:
OJ链接:https://leetcode.cn/problems/linked-list-cycle-ii/submissions/383864186/
知识普及: 环的追及问题,可借助下图进行理解:
例12:删除中间节点。
题目: 若链表中的某个节点,既不是链表头节点,也不是链表尾节点,则称其为该链表的中间节点。假定已知链表的某一个中间节点,请实现一种算法,将该节点从链表中删除。例如,传入节点 c(位于单向链表 a->b->c->d->e->f 中),将其删除后,剩余链表为 a->b->d->e->f。
思路: 替换法删除,如下图所示:
OJ链接:https://leetcode.cn/problems/delete-middle-node-lcci/submissions/384117833/
例13:旋转链表。
题目: 给你一个链表的头节点 head ,旋转链表,将链表每个节点向右移动 k 个位置。
OJ链接:https://leetcode.cn/problems/rotate-list/submissions/384128635/
4 顺序表和链表的区别
顺序表:
优点:空间连续、支持随机访问。
缺点:
- 中间或前面部分的插入删除时间复杂度O(N) 。
- 增容的代价比较大。
链表:
优点:
- 任意位置插入删除时间复杂度为O(1)。
- 没有增容问题,插入一个开辟一个空间。
缺点:以节点为单位存储,不支持随机访问。