LinkedList相关面试题

1. LinkedList 和 ArrayList 的区别是什么?

解答:

LinkedListArrayList 都实现了 List 接口,但它们的底层实现不同,因此它们的性能特性也有所不同。

  • 实现方式

    • ArrayList 基于动态数组实现,元素是连续存储的,支持通过索引随机访问。

    • LinkedList 基于双向链表实现,每个节点包含一个元素和指向前后节点的指针。

  • 随机访问性能

    • ArrayList 提供 O(1) 的随机访问性能,通过索引可以直接访问任何元素。

    • LinkedList 提供 O(n) 的访问性能,需要通过遍历节点来访问指定索引的元素。

  • 插入和删除性能

    • ArrayList 在中间插入或删除元素时,需要移动数组中的其他元素,时间复杂度是 O(n)。

    • LinkedList 在插入或删除操作时,只需要修改节点的前后指针,时间复杂度是 O(1),前提是你已经定位到节点。

  • 内存消耗

    • ArrayList 的元素是存储在一个连续的内存块中,内存使用较为紧凑。

    • LinkedList 每个元素都包含额外的前后指针(通常是 2 个引用),内存开销较大。

ArrayListLinkedList 的源码分析:
java 复制代码
// LinkedList 的节点类
private static class Node<E> {
    E item;
    Node<E> next;
    Node<E> prev;
    
    Node(Node<E> prev, E element, Node<E> next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}

private transient Node<E> first;  // 链表的头节点
private transient Node<E> last;   // 链表的尾节点
  • LinkedList 使用 Node 类来表示链表中的每个节点,节点存储数据以及前后指针,保证了双向链表的操作。

2. LinkedList 中如何进行元素插入和删除?

解答:

LinkedList 的插入和删除操作都基于链表结构,通过调整节点的前后指针来完成。其时间复杂度是 O(1),但前提是你已经定位到操作的节点。

插入操作:
  • 在链表的头部插入元素:addFirst()offerFirst()

  • 在链表的尾部插入元素:addLast()offerLast()

  • 在指定位置插入元素:add(int index, E element)

删除操作:
  • 删除链表头部元素:removeFirst()pollFirst()

  • 删除链表尾部元素:removeLast()pollLast()

  • 删除指定元素:remove(Object o)remove(int index)

插入和删除的源码分析:
java 复制代码
// 插入头部
public void addFirst(E e) {
    linkFirst(e);
}

private void linkFirst(E e) {
    final Node<E> f = first;
    final Node<E> newNode = new Node<>(null, e, f);
    first = newNode;
    if (f == null)
        last = newNode;
    else
        f.prev = newNode;
    size++;
    modCount++;
}

// 删除头部
public E removeFirst() {
    final Node<E> f = first;
    if (f == null) throw new NoSuchElementException();
    final E element = f.item;
    final Node<E> next = f.next;
    first = next;
    if (next == null)
        last = null;
    else
        next.prev = null;
    f.item = null;
    size--;
    modCount++;
    return element;
}
  • addFirst() 方法插入新节点到链表头部,removeFirst() 方法从链表头部删除元素。

  • 每次插入和删除时,通过调整相邻节点的 nextprev 指针,确保链表结构的正确性。

3. LinkedList 是否支持快速随机访问?

解答:

LinkedList 不支持快速随机访问。它的元素是通过链表节点连接的,每次访问元素都需要从头节点或尾节点开始遍历链表,直到找到目标节点。

get(int index) 方法源码分析:
java 复制代码
public E get(int index) {
    checkElementIndex(index);
    return node(index).item;
}

private Node<E> node(int index) {
    if (index < (size >> 1)) {
        Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {
        Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}
  • get() 方法首先检查索引是否有效,然后通过 node() 方法遍历链表来获取指定位置的节点。

  • 由于需要遍历链表,访问的时间复杂度是 O(n),这与 ArrayList 的 O(1) 随机访问不同。

4. LinkedList 的线程安全吗?

解答:

LinkedList 不是线程安全的。在多个线程同时访问和修改同一个 LinkedList 时,可能会导致数据不一致的问题。LinkedList 需要显式的同步机制来保证线程安全。

线程安全实现:
  • 如果需要线程安全的链表,可以使用 Collections.synchronizedList() 来包装 LinkedList,或者使用 CopyOnWriteArrayList(适用于读多写少的场景)。
java 复制代码
List<Integer> synchronizedList = Collections.synchronizedList(new LinkedList<>());

5. LinkedList 中如何查找元素的索引?

解答:

LinkedList 提供了 indexOf()lastIndexOf() 方法来查找元素的索引。

indexOf() 方法源码分析:
java 复制代码
public int indexOf(Object o) {
    if (o == null) {
        for (Node<E> x = first; x != null; x = x.next) {
            if (x.item == null)
                return indexOfNull(x);
        }
    } else {
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item))
                return indexOfNonNull(o, x);
        }
    }
    return -1;
}
  • indexOf() 方法遍历链表,从头节点开始,依次检查每个节点的值,直到找到匹配的元素。时间复杂度是 O(n),因为需要遍历链表。

6. LinkedList 中的 offerFirst() offerLast() 方法是什么?

解答:

offerFirst()offerLast() 方法是 Deque 接口中的方法,用于在链表的头部和尾部插入元素,分别是 addFirst()addLast() 方法的替代。

  • offerFirst(E e):在链表头部插入元素,如果成功返回 true

  • offerLast(E e):在链表尾部插入元素,如果成功返回 true

offerFirst() 方法源码分析:
java 复制代码
public boolean offerFirst(E e) {
    addFirst(e);
    return true;
}
  • offerFirst() 方法其实是调用 addFirst() 来在链表头部插入元素。

7. LinkedList 中的 remove(Object o) 方法如何工作?

解答: remove(Object o) 方法删除链表中第一次出现的指定元素。如果找到了元素,删除它,并调整相邻节点的指针。

remove(Object o) 方法源码分析:
java 复制代码
public boolean remove(Object o) {
    if (o == null) {
        for (Node<E> x = first; x != null; x = x.next) {
            if (x.item == null) {
                unlink(x);
                return true;
            }
        }
    } else {
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item)) {
                unlink(x);
                return true;
            }
        }
    }
    return false;
}

private void unlink(Node<E> x) {
    final E element = x.item;
    final Node<E> next = x.next;
    final Node<E> prev = x.prev;

    if (prev == null)
        first = next;
    else
        prev.next = next;

    if (next == null)
        last = prev;
    else
        next.prev = prev;

    x.item = null;
    size--;
    modCount++;
}
  • remove(Object o) 方法遍历链表,找到元素后通过 unlink() 方法断开该节点与前后节点的链接,从而删除该节点。

8. 如何反转一个 LinkedList?

解答: 可以通过遍历链表并调整每个节点的 nextprev 指针来实现链表的反转。

java 复制代码
public void reverse() {
    Node<E> current = first;
    Node<E> temp = null;
    while (current != null) {
        temp = current.prev;
        current.prev = current.next;
        current.next = temp;
        current = current.prev;
    }
    if (temp != null) {
        first = temp.prev;
    }
}
  • 通过反转每个节点的前后指针,最终完成链表的反转。

总结

  • LinkedList 的性能特点是插入和删除操作(特别是头尾操作)效率较高,但访问元素的效率较低。

  • 它使用双向链表实现,每个节点包含数据和指向前后节点的指针。

  • 插入和删除操作时间复杂度是 O(1),访问操作(通过索引)时间复杂度是 O(n)。

  • 线程不安全,使用时需要注意同步问题。

不积跬步,无以至千里 --- xiaokai

相关推荐
2401_833788051 小时前
Scala的模式匹配(2)
java·开发语言
悠悠龙龙2 小时前
框架模块说明 #05 权限管理_03
java·开发语言·spring
霖大侠2 小时前
Adversarial Learning forSemi-Supervised Semantic Segmentation
人工智能·算法·机器学习
开心羊咩咩3 小时前
Idea 2024.3 突然出现点击run 运行没有反应,且没有任何提示。
java·ide·intellij-idea
waterme1onY3 小时前
IDEA中MAVEN的一些设置问题
java·maven·intellij-idea
阿华的代码王国3 小时前
【算法】——前缀和(矩阵区域和详解,文末附)
java·开发语言·算法·前缀和
Sunyanhui13 小时前
力扣 LCR训练计划2(剑指 Offer 22. 链表中倒数第k个节点)-140
算法·leetcode·链表
yours_Gabriel3 小时前
【力扣】3274. 检查棋盘方格颜色是否相同
算法·leetcode
Chandler244 小时前
蓝桥杯经验分享
经验分享·算法·蓝桥杯
是老余4 小时前
算法基础之链表:移除链表元素leetcode203
数据结构·算法·链表