链表理论基础
0.链表基础
-
由指针串联每个节点构成的线性结构
-
每个节点包括数据域(data)和指针域(next)
-
类型:
- 单链表:next指向下一个节点
- 双链表:既有next,又有pre(pre指向前一个节点)
- 循环链表:收尾相连,即最后一个结点的next指向head的data
-
存储方式
- 由于链表是根据指针对数据进行串联的,其在内存中并不是连续存储的,而是散乱的分布在内存中的某地址上,具体根据操作系统的内存管理决定。
-
定义:手写链表
- 注意:如果不定义构造函数使用默认构造函数的话,在初始化的时候就不能直接给变量赋值
-
操作:
-
删除
-
添加
-
链表的增添和删除都是O(1)操作,也不会影响到其他节点。
但是要注意,要是删除最后一个节点,需要从头节点查找到倒数第第二个结点通过next指针进行删除操作,查找的时间复杂度是O(n)。
-
-
vs数组
- 数组适用于数据量固定,频繁查询,增删较少
- 链表相反
1.移除链表元素
通过设置虚拟节点来使删除所有节点的逻辑相同,pre.next = cur.next;
最后记得返回的是 dummy.next 而不是head ;因为dummy永远指向头结点,而head随着遍历不断向后移动,pre也不断向后移动
2.设计链表
结合上一个问题,使用虚拟前驱结点来使 处理所有节点的逻辑相同,更容易复现代码
- val / next / size / head = new ListNode(0)初始化虚拟头结点
- 增加节点时,注意建立cur当前节点和next节点的联系(cur.next = pre.next)
- 如果先建立pre和cur的联系(pre.next = cur) 会导致next结点的丢失
3.反转链表
- 迭代&递归方式都可
- 区别,递归空间复杂度O(n),迭代空间复杂度O(1);时间复杂度相等
- 理解为什么要用指针保存next&pre
- 初始状态下有head和head.next是可以进行操作的节点
- 显然pre需要保存,因为无法直接访问到
- 那么next为什么要保存呢?因为要进行反转操作,head.next 指向了pre,从而丢失了next节点,因此需要保存next
- 这样的话有两个变量pre,next即可;通过测试,将cur全部换成head一样可以成功运行代码