力扣链表专题 -1 单链表与双链表的预备知识

取自力扣图书区
先学习下预备知识 然后再开始刷题咯~

链表是面试非常高频的问题

尤其是那道经典的反转链表 虽然我目前看的面经不是很多 但是这四个字就没少看见 所以——这部分要好好刷一下!

与数组相似,链表也是一种线性数据结构

这里有一个例子:

img

正如你所看到的,链表中的每个元素实际上是一个单独的对象,而所有对象都通过每个元素中的引用字段链接在一起。

链表有两种类型:单链表和双链表。上面给出的例子是一个单链表,这里有一个双链表的例子:

img

我们将在接下来的章节中介绍更多内容。完成后,你将:

  • 了解单链表和双链表的结构
  • 在单链表和双链表中实现遍历、插入和删除
  • 分析在单链表或双链表中的各种操作的复杂度;
  • 在链表中使用双指针技巧(快指针慢指针技巧)
  • 解决一些经典问题,例如反转链表
  • 分析你设计的算法的复杂度
  • 积累设计和调试的经验。

1.单链表

正如我们在概览中提到的那样,链表是一种线性数据结构,它通过引用字段将所有分离的元素链接在一起。有两种常用的链表:单链表双链表

本章节中,我们将从单链表开始,并帮助您:

  • 了解单链表的结构
  • 在单链表中执行遍历插入删除操作;
  • 分析单链表中不同操作的时间复杂度

【1】单链表简介

单链表中的每个结点不仅包含值,还包含链接到下一个结点的引用字段。通过这种方式,单链表将所有结点按顺序组织起来。

面是一个单链表的例子:

img

蓝色箭头显示单个链接列表中的结点是如何组合在一起的。

结点结构

以下是单链表中结点的典型定义:

public class SinglyListNode{
    int val;
    SinglyListNode next;
    SinglyListNode(int x){val = x;}
}

在大多数情况下,我们将使用头结点(第一个结点)来表示整个列表。

单链表的操作

与数组不同,我们无法在常量时间内访问单链表中的随机元素。 如果我们想要获得第 i 个元素,我们必须从头结点逐个遍历。我们按索引访问元素平均要花费 O(N)时间,其中 N 是链表的长度。

例如,在上面的示例中,头结点是 23。访问第 3 个结点的唯一方法是使用头结点中的“next”字段到达第 2 个结点(结点 6) 然后使用结点 6 的“next”字段,我们能够访问第 3 个结点。

你可能想知道为什么链表很有用,尽管它在通过索引访问数据时(与数组相比)具有如此糟糕的性能。

在接下来我们将介绍插入和删除操作,你将了解到链表的好处。

之后,我们将为你提供练习设计自己的单链表

【2】单链表的添加操作

如果我们想在给定的结点 prev 之后添加新值,我们应该:

  • 使用给定值初始化新结点 cur
    img

  • curnext 字段链接到 prev 的下一个结点 next
    img

  • prev 中的 next 字段链接到 cur
    img

一句话——让新来的节点有所指向(next),从头遍历找到前一个节点(prev)指向新来的节点

与数组不同,我们不需要将所有元素移动到插入元素之后。(和前面的数组专题做一个对比~)因此,您可以在 O(1) 时间复杂度中将新结点插入到链表中,这非常高效。

示例


img

让我们在第二个结点 6 之后插入一个新的值 9 。

我们将首先初始化一个值为 9 的新结点。然后将结点 9 链接到结点 15 。最后,将结点 6 链接到结点 9 。

插入之后,我们的链表将如下所示:

img

在开头添加结点


众所周知,我们使用头结点来代表整个列表。

因此,在列表开头添加新节点时更新头结点 head 至关重要

  • 初始化一个新结点 cur

  • 将新结点链接到我们的原始头结点 head

  • cur 指定为 head

例如,让我们在列表的开头添加一个新结点 9 。

  1. 我们初始化一个新结点 9 并将其链接到当前头结点 23 。
    img
  2. 指定结点 9 为新的头结点。
    img

思考一个问题

如何在列表的末尾添加新的结点?我们还能使用类似的策略吗?

while(head.next != null){
    //移动head指针
    head = head.next;
}
//移动到尾部之后 将新节点插入尾部
head.next = newNode;
newNode.next = null;

【3】单链表的删除操作

如果我们想从单链表中删除现有结点 cur,可以分两步完成:

  • 找到 cur 的上一个结点 prev 及其下一个结点 next
    img

  • 接下来链接 prev 到 cur 的下一个节点 next
    img

在我们的第一步中,我们需要找出 prevnext

使用 cur 的参考字段很容易找出 next,但是,我们必须从头结点遍历链表,以找出 prev —— 它的平均时间是 O(N),其中 N 是链表的长度。因此,删除结点的时间复杂度将是 O(N)

空间复杂度为 O(1),因为我们只需要常量空间来存储指针。

示例


img

让我们尝试把结点 6从上面的单链表中删除。

  1. 从头遍历链表,直到我们找到前一个结点 prev,即结点 23

  2. prev(结点 23)与 next(结点 15)链接

img

结点 6 现在不在我们的单链表中。

删除第一个结点


如果我们想删除第一个结点,策略会有所不同。

正如之前所提到的,我们使用头结点 head 来表示链表。我们的头是下面示例中的黑色结点 23。

img

如果想要删除第一个结点,我们可以简单地将下一个结点分配给 head。也就是说,删除之后我们的头将会是结点 6。

img

链表从头结点开始,因此结点 23 不再在我们的链表中。

思考一个问题

删除最后一个结点呢?我们还能使用类似的策略吗?

——把倒数第二个节点的next指向NULL即可

2.双链表

本段内容都来自 leetbook

【1】双链表的简介

我们在前面的章节中介绍了单链表。

单链接列表中的结点具有 Value 字段,以及用于顺序链接结点的“Next”引用字段。

在本文中,我们将介绍另一种类型的链表:双链表

定义


双链表以类似的方式工作,但还有一个引用字段,称为“prev”字段。有了这个额外的字段,您就能够知道当前结点的前一个结点。

让我们看一个例子:

img

绿色箭头表示我们的“prev”字段是如何工作的。

结点结构


下面是双链表中结点结构的典型定义:

//cpp
struct DoubleListNode{
    int val;
    DoubleListNode *next, *prev;
    DoubleListNode(int x): val(x), next(NULL),prev(NULL){}
}
//java
class DoubleListNode{
    int val;
    DoubleListNode next, prev;
    DoubleListNode(int x){
        VAL = X;
    }
}

与单链接列表类似,我们将使用头结点来表示整个列表。

操作


与单链表类似,我们将介绍在双链表中如何访问数据、插入新结点或删除现有结点。

我们可以用 和单链表相同的方式访问数据

同单链表一样——

  • 我们不能在常量级的时间内访问随机位置
  • 我们必须从头部遍历才能得到我们想要的第一个结点。
  • 在最坏的情况下,时间复杂度将是 O(N),其中 N 是链表的长度。

对于添加和删除,会稍微复杂一些,因为我们还需要处理“prev”字段。在接下来的两篇文章中,我们将介绍这两个操作

之后,我们提供练习,让你使用双链表重新设计链表。

【2】添加操作-双链表

如果我们想在现有的结点 prev 之后插入一个新的结点 cur,我们可以将此过程分为两个步骤

  1. 链接 curprevnext,其中 nextprev 原始的下一个节点;
    img
  2. cur 重新链接 prevnext
    img

与单链表类似,添加操作的时间和空间复杂度都是O(1)

示例


img

让我们在现有结点 6 之后添加一个新结点 9:

  1. 链接 ==cur(结点 9)==与 prev(结点 6)和 next(结点 15)
    img
  2. 用==cur(结点 9)==重新链接 prev(结点 6)和 next(结点 15)
    img

如果我们想在开头结尾插入一个新结点怎么办?

使用虚拟头节点和尾节点,添加操作就统一了。”

【3】删除操作 - 双链表

如果我们想从双链表中删除一个现有的结点 cur,我们可以简单地

将它的前一个结点 prev 与下一个结点 next 链接起来。

与单链表不同使用“prev”字段可以很容易地在常量时间内获得前一个结点

因为我们不再需要遍历链表来获取前一个结点,所以时间和空间复杂度都是O(1)

示例


img

我们的目标是从双链表中删除结点 6。

因此,我们将它的前一个结点 23 和下一个结点 15 链接起来

img

结点 6 现在不在我们的双链表中,达成效果。

如果我们要删除第一个结点最后一个结点怎么办?

这部分内容可以再好好思考下

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值