LeetCode 160:相交链表

先补充数组和动态数组的区别,再比较动态数组和链表的区别,最后讲解本题题解。

数组和动态数组

1. 普通数组(静态数组,Static Array)

  • 内存分配 :一旦定义,大小固定(如 C 语言中的 int arr[5])。

  • 缺点:如果满了就不能再加元素,想扩容必须手动申请新数组并复制数据。

  • 优点:结构简单,内存连续,访问下标 O(1)。


2. 动态数组(Dynamic Array)

  • 内存分配 :底层还是数组,但容量不足时会自动申请更大空间并复制元素,对用户是透明的。

  • 扩容策略:常见是扩容为当前容量的 1.5~2 倍,以减少频繁复制。

  • 典型实现

    • Python 的 list

    • Java 的 ArrayList

    • C++ 的 std::vector

动态数组(Dynamic Array)和链表(Linked List)

两种典型的线性数据结构,它们在内存分配、访问方式、插入删除效率等方面有明显区别:


1. 内存分配方式

  • 动态数组 (如 Python 的 list):

    • 在内存中分配一块连续的空间来存放元素。

    • 如果容量不够,会一次性申请更大的空间(通常是扩容 1.5~2 倍),然后把旧数据拷贝过去。

  • 链表

    • 每个元素是一个节点 ,包含数据和指向下一个节点的指针(next)。

    • 节点分散在内存中,通过指针链接,不要求连续内存。


2. 元素访问效率

  • 动态数组 :支持随机访问,arr[i] 时间复杂度 O(1),因为可以直接通过偏移量定位。

  • 链表 :只能从头节点开始依次遍历,查找第 i 个元素的时间复杂度 O(n)


3. 插入与删除效率

  • 动态数组

    • 在尾部插入/删除:O(1)(摊销)

    • 在中间插入/删除:O(n),因为需要移动大量元素。

  • 链表

    • 已知节点的前驱节点时,插入/删除节点:O(1)

    • 但如果要先找到插入位置,查找过程是 O(n)


4. 空间利用率

  • 动态数组:可能存在额外的预留空间(扩容时会浪费部分内存)。

  • 链表:每个节点需要额外的指针域,指针占用额外内存,尤其数据量小的时候浪费更明显。


5. 适用场景总结

  • 如果访问多、随机读取多 :用动态数组更高效(例如 Python 的 list)。

  • 如果频繁插入删除 ,尤其在中间或两端:链表更适合(如 collections.deque 用的是双向链表+块结构)。


直观对比表

特性 动态数组(Python list) 链表(Linked List)
内存结构 连续内存 分散内存
随机访问效率 O(1) O(n)
插入/删除效率 中间 O(n),尾部 O(1) 已知节点 O(1),查找 O(n)
空间开销 可能有预留空间 每个节点多一个指针
扩容成本 有扩容成本(复制数据) 无扩容成本

LeetCode 160:相交链表

思路

在单链表题里,传进来的两个"头节点"headA、headB 就分别代表两条链表:每个头节点是第一个实际节点的引用,你可以从它开始沿着 .next 指针一直走到结尾把整条链表遍历出来。相交判定是看节点是否同一对象(p is q),不是看 val 是否相等

解题过程

把两个头节点headA,headB先赋值分别赋值给p,q,为了不改变头节点信息,当p的val和q的val不为null时,p走一步,q走一步,相同时返回,不相同一直遍历下去,A遍历完遍历B,B遍历完遍历A

复杂度

  • 时间复杂度: O(m+n)O(m+n)O(m+n)

  • 空间复杂度: O(1) O(1) O(1)