搞懂单链表常见面试题

这篇博客详细探讨了单链表的基础概念、基本操作,包括获取长度、查询节点、添加和删除元素。接着,重点讲解了单链表的面试常考题目,如寻找中间元素、判断循环链表、求倒数第N个节点、删除倒数第n个节点、旋转和翻转链表等,以及排序和合并链表的方法。通过实例解析和代码展示,帮助读者深入理解和应对单链表相关的面试挑战。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这里写图片描述

搞懂单链表常见面试题

Hello 继上次的 搞懂基本排序算法,这个一星期,我总结了,我所学习和思考的单链表基础知识和常见面试题,这些题有的来自 《剑指 offer》 ,有的来自《程序员代码面试指南》,有的来自 leetCode,不是很全面,但都具有一定代表性,相信大家看完以后一定跟我一样,对面试的时候算法题又多了一份自信。

什么是单链表

链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer),简单来说链表并不像数组那样将数组存储在一个连续的内存地址空间里,它们可以不是连续的因为他们每个节点保存着下一个节点的引用(地址),所以较之数组来说这是一个优势。

对于单链表的一个节点我们经常使用下边这种代码表示:

public class Node{
    //节点的值
    int value//指向下一个节点的指针(java 中表现为下一个节点的引用)
    Node next;

    public void Node(int value){
        this.value = value;
    }
}

单链表的特点

  1. 链表增删元素的时间复杂度为O(1),查找一个元素的时间复杂度为 O(n);
  2. 单链表不用像数组那样预先分配存储空间的大小,避免了空间浪费
  3. 单链表不能进行回溯操作,如:只知道链表的头节点的时候无法快读快速链表的倒数第几个节点的值。

单链表的基本操作

上一节我们说了什么是单链表,那么我们都知道一个数组它具有增删改查的基本操作,那么我们单链表作为一种常见的数据结构类型也是具有这些操作的那么我们就来看下对于单链表有哪些基本操作:

获取单链表的长度

由于单链表的存储地址不是连续的,链表并不具有直接获取链表长度的功能,对于一个链表的长度我们只能一次去遍历链表的节点,直到找到某个节点的下一个节点为空的时候得到链表的总长度,注意这里的出发点并不是一个空链表然后依次添加节点后,然后去读取已经记录的节点个数,而是已知一个链表的头结点然后去获取这个链表的长度:

public int getLength(Node head){

    if(head == null){
        return 0;
    }

    int len = 0;
    while(head != null){
        len++;
        head = head.next;
    }  
    return len;  
}

查询指定索引的节点值或指定值得节点值的索引

由于链表是一种非连续性的存储结构,节点的内存地址不是连续的,也就是说链表不能像数组那样可以通过索引值获取索引位置的元素。所以链表的查询的时间复杂度要是O(n)级别的,这点和数组查询指定值得元素位置是相同的,因为你要查找的东西在内存中的存储地址都是不一定的。

    /** 获取指定角标的节点值 */
    public int getValueOfIndex(Node head, int index) throws Exception {

        if (index < 0 || index >= getLength(head)) {
            throw new Exception("角标越界!");
        }

        if (head == null) {
            throw new Exception("当前链表为空!");
        }

        Node dummyHead = head;

        while (dummyHead.next != null && index > 0) {
            dummyHead = dummyHead.next;
            index--;
        }

        return dummyHead.value;
    }


    /** 获取节点值等于 value 的第一个元素角标 */
    public int getNodeIndex(Node head, int value) {

            int index = -1;
            Node dummyHead = head;

            while (dummyHead != null) {
                index++;
                if (dummyHead.value == value) {
                    return index;
                }
                dummyHead = dummyHead.next;
            }

            return -1;
    }

链表添加一个元素

学过数据结构的朋友一定知道链表的插入操作,分为头插法,尾插法,随机节点插入法,当然数据结构讲得时候也是针对一个已经构造好的(保存了链表头部节点和尾部节点引用)的情况下去插入一个元素,这看上去很简单,如果我们在只知道一个链表的头节点的情况下去插入一个元素,就不是那么简单了,就对于头插入法我们只需要构造一个新的节点,然后将这个节点的 next 指针指向已知链表的头节点就可以了。

1、 在已有链表头部插入一个节点

public Node addAtHead(Node head, int value){
     Node newHead = new Node(value);
     newHead.next = head;
     return newHead;
}

2、在已有链表的尾部插入一个节点:

public void addAtTail(Node head, int value){
     Node node = new Node(value);
     Node dummyHead = head;

     //找到未节点 注意这里是当元素的下一个元素为空的时候这个节点即为未节点
     while( dummyHead.next != null){
        dummyHead = dummyHead.next;
     }

     dummyHead.next = node;   
}

3、在指定位置添加一个节点

// 注意这里 index 从 0 开始
 public Node insertElement(Node head, int value, int index) throws Exception {
   //为了方便这里我们假设知道链表的长度
   int length = getLength(head);
   if (index < 0 || index >= length) {
       throw new Exception("角标越界!");
   }

   if (index == 0) {
       return addAtHead(head, value);
   } else if (index == length - 1) {
       addAtTail(head, value);
   } else {

       Node pre = head;
       Node cur = head.next;
       //
       while (pre != null && index > 1) {
           pre = pre.next;
           cur = cur.next;
           index--;
       }

       //循环结束后 pre 保存的是索引的上一个节点 而 cur 保存的是索引值当前的节点
       Node node = new Node(value);
       pre.next = node;
       node.next = cur;
   }
   return head;
}

在指定位置添加一个节点,首先我们应该找到这个索引所在的节点的前一个,以及该节点,分别记录这两个节点,然后将索引所在节点的前一个节点的 next 指针指向新节点,然后将新节点的 next 指针指向插入节点即可。与其他元素并没有什么关系,所以单链表插入一个节点时间复杂度为 O(1),而数组插入元素就不一样了如果将一个元素插入数组的指定索引位置,那么该索引位置以后元素的索引位置(内存地址)都将发生变化,所以一个数组的插入一个元素的时间复杂度为 O(n);所以链表相对于数组插入的效率要高一些,删除同理。

链表删除一个元素

由于上边介绍了链表添加元素的方法这里对于链表删除节点的方法不在详细介绍直接给出代码:

1、 删除头部节点 也就是删除索引为 0 的节点:

    public Node deleteHead(Node head) throws Exception {
        if (head == null) {
            throw new Exception("当前链表为空!");
        }
        return head.next;
    }

2、 删除尾节点

    public void deleteTail(Node head) throws Exception {

        if (head == nul
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值