leetcode刷题——LinkedList

前言

链表数据结构属于链式数据结构(链表、树、图)中最简单的一种形式,在题目中主要是考察遍历、复制、倒序、增加节点、删除节点等操作,上周我用了3天时间刷完了教程,做了大概20道题目。


常见操作与编程技巧

一个链表中的节点可以用下面来表示(python语言):

class ListNode(object):
    def __init__(self, x):
        self.val = x
        self.next = None

值存储在val中,同时存储了下一个节点的地址。

编程中用到的常见操作包括:

  1. 遍历链表获得长度。时间复杂度O(N)。
  2. 查找某个位置的节点。时间复杂度O(N)。
  3. 改变下一个节点的地址。查找时间复杂度O(N)。
  4. 生成反序链表。时间复杂度同样O(N)。

一些编程技巧:

  1. 快慢双指针。单向链表问题往往只能用当前节点做文章,对于比较、复制节点、删除节点一个指针无法完成,因此设置快慢指针可以一前一后完成相应操作。
  2. 循环链表。遇到循环类问题可以首尾相连试试解决方案。
  3. 遍历时候的while和for。由于链表之存储了下一个节点的地址,因此不可能建立索引,也就没法使用for循环进行迭代操作,因此只能用while形式通过指针形式向后跳转。
  4. dummy_point。遍历单向链表后头部节点会找不到,因此必须在最开始声明一个指针,指向头节点。

关于数据结构的更多思考

从遍历的角度来看,数组、队列、栈、链表、树、图、散列表的实现可以分成以下两种情况:

  1. 数据结构内节点地址直接存储、直接获得,我们称作直接寻址
  2. 数据结构内节点地址通过函数计算获得,我们称作函数寻址

先解释下这两种情况。

我们说无论上面哪种数据结构,它肯定是由一个个节点组成的,否则他就是一个变量了。因此如何组织起这些数据节点,这就是结构问题,那么就是我们谈到的“数据结构”。

而一说到“数据结构”,不谈增删改等操作,单单只来看最基础的操作查,也就是遍历,对于每一种数据结构,那我们需要至少以下三个要素:

  1. 得到初始节点。
  2. 得到下一个节点地址,获得值,并重复此过程。
  3. 找到最后一个节点结束步骤2。

在这里,第一个步骤显而易见,你只有得到初始的节点位置,才能遍历,否则偌大的内存地址空间,没有位置,怎么确定从哪里开始。第三个步骤也容易理解,数据结构内的节点数量一定是有限的,因此必然通过第二个步骤找到最后的节点结束整个过程。第二个步骤,关键就是如何存储和得到节点地址?

我们可以仔细想想最基础的数组、散列表、链表是如何得到节点地址的:

  1. 数组很简单,通过索引,也就是下标,来计算从第一个节点隔几个取下一节点地址。我们可以通过表达式A(n) = A(0)+N来表示,这就是通过函数进行查找地址,这就是函数寻址
  2. 散列表比较特殊。查找下一个节点或者在这里应该说查找某个节点,与所谓的初始节点没什么关系,我们可以通过表达式A(n)=f(N)来表达,这里没有A(0)什么事情,但显而易见的是它也是通过函数来查找地址,只不过它是通过hash的形式,不过是一种特殊的函数形式,需要处理桶内的再次查找问题,这就是函数寻址
  3. 链表。链表要查找第N个节点,没办法要先找到第N-1、N-2个节点,我们可以这样表示A(n)=A(n-1).next表示。这里等号的右边还需要用到函数关系A(),因此这是一个递推过程,也可以叫函数的迭代。很明显计算A(n)无法通过函数一次解决,这就是直接寻址

所以1,2的数组、散列表只需要一次函数的计算就可以获得节点地址,时间复杂度为O(1)。3链表需要多次函数计算获得节点地址,时间复杂度为O(N)。

再来看更复杂的队列、栈、树、图:

  1. 队列、栈。栈使用函数寻址直接寻址实现都没有问题,因为栈的查找节点只需要在一端进行,用数组很直观,只要一直计算A(N-1)函数获得地址即可,用链表也可以实现使用快慢指针指向末尾两个节点即可。列表也是同样的道理,使用A(0),A(N-1)函数计算地址,或者使用链表,多个指针维护队列头尾。之所以这两种数据结构使用函数寻址直接寻址两种方式都可以实现,原因就在于他们是连续的存储方式,便于如同数组一样函数寻址,同时常见遍历范围只局限于尾部或头尾一道两个节点,使用链表加几个指针直接寻址也不困难。
  2. 树。树使用类似链表的直接寻址方式就划算的多,因为它存储的下一个节点是多个子节点,使用函数计算的函数寻址方式就很难得到下一个节点(除非是堆这种完全二叉树形式,能把字节点地址精确映射到指定位置),不如直接存储子节点的地址划算的来。并且由于树天生的结构优势,它的查找在遍历过程中,时间复杂度更是能低到O(logN)。
  3. 图。图的邻接表存储方式结合了函数寻址直接寻址的方式。我们把图中所有节点放在一个连续的数组内,用来函数寻址,把每个节点的相邻节点的地址存放在每个节点的值中,这就是直接寻址。通过两种方式的结合,我们能够很方便的在每个节点来回遍历。

其实说了这么多数据结构与函数寻址直接寻址,我们仔细想想,这些数据结构都是抽象的,我们在脑海中可以描绘出各种数据结构的形状,遍历方式,可是在计算机中,它遍历时候寻址方式就这两种,要么通过函数计算,要么直接取地址值。


结语

我们今后再来看这些抽象的数据结构时候,或许可以更进一步的从寻址的角度来理解他们之间的特点,更灵活的找到不同数据结构的使用场景。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值