链表面试题的总结和思路分享

news2024/11/30 2:28:39

꒰˃͈꒵˂͈꒱ write in front ꒰˃͈꒵˂͈꒱
ʕ̯•͡˔•̯᷅ʔ大家好,我是xiaoxie.希望你看完之后,有不足之处请多多谅解,让我们一起共同进步૮₍❀ᴗ͈ . ᴗ͈ აxiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客
本文由xiaoxieʕ̯•͡˔•̯᷅ʔ 原创 CSDN 如需转载还请通知˶⍤⃝˶
个人主页:xiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客
系列专栏:xiaoxie的刷题系列专栏——CSDN博客●'ᴗ'σσணღ*
我的目标:"团团等我💪( ◡̀_◡́ ҂)" 

( ⸝⸝⸝›ᴥ‹⸝⸝⸝ )欢迎各位→点赞👍 + 收藏⭐️ + 留言📝​+关注(互三必回)! 

 

链表篇(点击标题即可进入力扣界面)

1.移除链表元素 

这是一道来自力扣的简单题但却有一些小细节值得注意,博主将画图为大家讲解,这题该如何解答

首先由题意以知,这是一个单链表其次就是等于val 的节点可以有多个,那我们是不是可以设置两个指针,一个为比较的节点,一个为比较的节点的前一个节点的指针

可以得到以下代码:

if(head == null) return null;
        ListNode prev = head;
        ListNode cur = head.next;

 然后我们就移动cur指针只要cur.val的值和val相等我们就删除该节点,那么好,既然要遍历指针,我们是不是首先确定好循环条件,避免发生空指针异常,由于因为是要遍历所有指针,所以呢循环条件为cur != null 其次还需要注意的一点就是,如何删,在这里博主有个小技巧就是只要是删除,首先,就是要先把要删除节点后面的部分,给联系上,否则就丢了。代码如下

while(cur != null) {
            if(cur.val == val) {
                prev.next = cur.next;//先连接后面的
            }else {
                prev = cur;
            }
              cur = cur.next;//无论是否找到都要移动cur
        }

这就完了吗,我想有许多细心的小伙伴可以发现头结点还没有比较呢,这个循环遍历的是把除了头结点以外的节点都遍历,当头节点的val等于val就少删除了一个节点所以在最后还需要判断一下头结点,完整代码如下

java版本:

class Solution {
    public ListNode removeElements(ListNode head, int val) {
     if(head == null) return null;
        ListNode prev = head;
        ListNode cur = head.next;
        while(cur != null) {
            if(cur.val == val) {
                prev.next = cur.next;
            }else {
                prev = cur;
            }
             cur = cur.next;
        }
         //判断头结点
        if(head.val == val) {
            head = head.next;
        }
        return head;
    }
}

 c++版本:

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        if (head == nullptr) {
            return nullptr;
        }
        ListNode* prev = head;
        ListNode* cur = head->next;
        while (cur != nullptr) {
            if (cur->val == val) {
                prev->next = cur->next;
                delete cur;
            } else {
                prev = cur;
            }
            cur = cur->next;
        }
      
        if (head->val == val) {
            ListNode* newHead = head->next;
            delete head;
            return newHead;
        }
        return head;
    }
};

至于为什么不先判断呢而是最后才判断是为了防止出现以下情况,所以我们把除了头结点以外的节点都遍历比较一遍,最后在比较即可

时间复杂度为O(n),空间复杂度为O(1) 

2.设计链表

要想学好数据结构,首先就得收悉它的底层实现,可以完成该题练习链表操作,是为后续的刷链表题的一个基础

JAVA版本

class MyLinkedList {
    //静态内部类
    static class LNode {
        public int val;
        public LNode next;
        public LNode(int val) {
            this.val = val;
        }
    }
     int size;
     LNode head;
    public MyLinkedList() {
        //size存储链表元素的个数
        int size;
        head = new LNode(0);
    }
    
    public int get(int index) {
        //判断index是否的合法
        if (index < 0 || index >= size) {
            return -1;
        }
        LNode cur = head;
         for (int i = 0; i <= index; i++) {
           cur = cur.next;
        }
        return cur.val;
    }
   
    //在链表最前面插入一个节点,等价于在第0个元素前添加
    public void addAtHead(int val) {
         addAtIndex(0, val);
    }
     //在链表的最后插入一个节点,等价于在(末尾+1)个元素前添加
    public void addAtTail(int val) {
        addAtIndex(size, val);
    }
    
    public void addAtIndex(int index, int val) {
         if (index > size) {
            return;
        }
        if (index < 0) {
            index = 0;
        }
        size++;
        LNode cur = head;
        for (int i = 0; i < index; i++) {
            cur = cur.next;
        }
        LNode toAdd = new LNode(val);
        toAdd.next = cur.next;
        cur.next = toAdd;
    }
    
    public void deleteAtIndex(int index) {
     if (index < 0 || index >= size) {
            return;
        }
        size--;
        if (index == 0) {
            head = head.next;
	    return;
        }
        LNode cur = head;
        for (int i = 0; i < index ; i++) {
            cur = cur.next;
        }
        cur.next = cur.next.next;
    }

}
 C++版本
#include <iostream>

class MyLinkedList {
private:
    // 定义链表节点的结构
    struct LNode {
        int val;
        LNode* next;
        LNode(int v) : val(v), next(nullptr) {}
    };
    
    int size;
    LNode* head;

public:
    // 构造函数用于初始化链表
    MyLinkedList() : size(0), head(new LNode(0)) {}

    // 获取指定索引处的值
    int get(int index) {
        if (index < 0 || index >= size) {
            return -1;
        }
        LNode* cur = head->next;
        for (int i = 0; i < index; i++) {
            cur = cur->next;
        }
        return cur->val;
    }

    // 在链表开头添加一个节点
    void addAtHead(int val) {
        addAtIndex(0, val);
    }

    // 在链表末尾添加一个节点
    void addAtTail(int val) {
        addAtIndex(size, val);
    }

    // 在指定索引处添加一个节点
    void addAtIndex(int index, int val) {
        if (index > size) {
            return;
        }
        if (index < 0) {
            index = 0;
        }
        size++;
        LNode* cur = head;
        for (int i = 0; i < index; i++) {
            cur = cur->next;
        }
        LNode* toAdd = new LNode(val);
        toAdd->next = cur->next;
        cur->next = toAdd;
    }

    // 删除指定索引处的节点
    void deleteAtIndex(int index) {
        if (index < 0 || index >= size) {
            return;
        }
        size--;
        LNode* cur = head;
        for (int i = 0; i < index; i++) {
            cur = cur->next;
        }
        LNode* temp = cur->next;
        cur->next = cur->next->next;
        delete temp;
    }
};

3.反转链表

 在这里博主给大家一个小建议,如果你是初学者,做数据结构这类的题目,首先就是需要画图来理解题意,这样才可以有好的思路

由图我们可以看出反转链表既是改变next指针的指向,我们只需要把节点前一个的地址等于节点后一个的地址就可以了

首先定义一个cur指针,指向头结点,再定义一个pre指针,初始化为null。

然后就要开始反转了,首先要把 cur->next 节点用tmp指针保存一下,也就是保存一下这个节点。

为什么要保存一下这个节点呢,因为接下来要改变 cur->next 的指向了,将cur->next 指向pre ,此时已经反转了第一个节点了。

接下来,就是循环走如下代码逻辑了,继续移动pre和cur指针。

最后,cur 指针已经指向了null,循环结束,链表也反转完毕了。 此时我们return pre指针就可以了,pre指针就指向了新的头结点

JAVA版本

class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode prev = null; // 将prev初始化为null
        ListNode cur = head; // 将cur初始化为head
        while(cur != null) { // 遍历链表
            ListNode tmp = cur.next; // 存储下一个节点
            cur.next = prev; // 反转指针
            prev = cur; // 将prev移到当前节点
            cur = tmp; // 将当前节点移到下一个节点
        }
        return prev; // 返回反转后的链表头节点
    }
}
 C++版本
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* prev = nullptr; // 将prev初始化为nullptr
        ListNode* cur = head; // 将cur初始化为head
        while (cur != nullptr) { // 遍历链表
            ListNode* tmp = cur->next; // 存储下一个节点
            cur->next = prev; // 反转指针
            prev = cur; // 将prev移到当前节点
            cur = tmp; // 将当前节点移到下一个节点
        }
        return prev; // 返回反转后的链表头节点
    }
};

4.两两交换链表中的节点

 一样需要用画图理解,解这题有两种方法分别是使用递归和哨兵节点的方法

1.递归

首先我们要清楚解决递归问题的关键点什么:

  1. 返回值
  2. 做了什么
  3. 终止条件
  4. 递归有传递就有归

这里博主先公布代码,如果递归看不懂的可以看博主的递归过程

Java版本:
class Solution {
    public ListNode swapPairs(ListNode head) {
        if(head == null || head.next == null) {
            return head;
        }
        ListNode newhead = head.next;
        head.next = swapPairs(newhead.next);
        newhead.next = head;
        return newhead;
    }
}
 C++版本
class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        if (head == nullptr || head->next == nullptr) {
            return head;
        }
        ListNode* newHead = head->next;
        head->next = swapPairs(newHead->next);
        newHead->next = head;
        return newHead;
    }
};

递归的过程

以 1 -> 2 -> 3 -> 4为例

第一步:1 -> 2 -> 3 -> 4  head = 1, newhead = 2;

第二步:head.next 1的下一个节点 为,swapairs(newhead.next) 进入递归

第三步:head = 3, newhead = 4,

第四步:  head.next 3 的下一个节点为swapairs(newhead.next) 进入递归。

第五步: head = null , newhead = null,所以要返回 null.

第六步: head.next 3 的下一个节点为swapairs(newhead.next)的返回值为null 此时 3的下一个节点就为null。

第七步 :newhead.next = head,也就是 4 -> 3,然后返回newhead,

第八步: head.next 1 的下一个节点为swapairs(newhead.next)的返回值为4,此时 3的下一个节点就为4。也就是1 -> 4 ->3.递归结束

第九步:newhead.next = head 所以 2 -> 1 ->4 -3.

如果你还是觉得很难理解,可以自己动手画画图,或者是复制粘贴一下博主的代码,在编译器上用调试功能看递归过程是如何实现的。同时博主这里还有一种解法

2.哨兵节点

哨兵节点(也被称为虚拟头节点或者哑节点)和头指针是两个不同的概念:

  • 哨兵节点:这是一个额外创建的节点,通常用于简化链表操作的代码。哨兵节点的next指针通常指向链表的头节点。哨兵节点本身不存储任何有效数据,它的主要作用是作为一个固定的节点,使得对链表头节点的操作和对其他节点的操作一致。

  • 头指针:这是一个指向链表头节点的指针。头指针通常用于表示整个链表,因为通过头指针,我们可以访问链表的所有节点。

在链表操作中,我们通常会创建一个哨兵节点,并让一个头指针指向这个哨兵节点。这样,我们就可以通过头指针来访问整个链表,包括哨兵节点。

根据图可知

Java版本 
class Solution {
    public ListNode swapPairs(ListNode head) {
     ListNode dummy = new ListNode(-1);//创建一个哨兵节点,它的下一个节点是头节点
     dummy.next = head;
     ListNode cur = dummy;// 创建一个临时节点,用于遍历链表
     while(cur.next != null && cur.next.next != null) {//因为要交换两个节点
     ListNode node1 = cur.next;
     ListNode node2 = cur.next.next;
     //交换过程
      node1.next = node2.next;//步骤1
      node2.next = node1;//步骤2
      cur.next = node2;//步骤3
      cur = node1;//移动cur到下一个要交换的节点
     }
     return dummy.next;
 }
}
C++版本
class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        ListNode* dummy = new ListNode(-1); // 创建一个哨兵节点,它的下一个节点是头节点
        dummy->next = head;
        ListNode* cur = dummy; // 创建一个临时节点,用于遍历链表
        while (cur->next != nullptr && cur->next->next != nullptr) { // 因为要交换两个节点
            ListNode* node1 = cur->next;
            ListNode* node2 = cur->next->next;
            node1->next = node2->next; // 步骤1
            node2->next = node1; // 步骤2
            cur->next = node2; // 步骤3
            cur = node1; // 移动cur到下一个要交换的节点
        }
        return dummy->next;
    }
};

5.删除链表的倒数第N个节点

这题我们可以使用快慢指针来做,同时考虑到要遍历所有的节点,所以还需要设置一个哨兵节点,同意博主通过画图来解这道题

 

先让快指针走n+1步,然后快慢指针在一起走 直到快指针指向null,慢指针指向的就是要删除的前一个节点(这也就是为什么快指针先走n+1步,让慢指针指向前一个,更好进行删除操作)

代码如下:

Java版本

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummy = new ListNode(-1);
        dummy.next = head;
        ListNode fast = dummy;
        ListNode slow = dummy;
        for(int i = 0;i <= n;i++) {
            fast = fast.next;
        }
        while(fast != null) {
            fast = fast.next;
            slow = slow.next;
        }
        slow.next = slow.next.next;
         return dummy.next;
    }
   
}
 C++版本
class Solution {
public:
    ListNode* swapPairs(ListNode* head,int n) {
        ListNode* dummy = new ListNode(-1); 
        dummy->next = head;
        ListNode*fast = dummy; 
        ListNode*slow = dummy; 
        for(int i = 0;i <= n;i++) 
        {
           fast = fast -> next;
         }
        while (fast != nullptr) { 
            fast = fast->next; 
            slow = slow->next; 
        }
            slow->next = slow ->next -> next;
        return dummy->next;
    }
};

6.环形链表II

以使用快慢指针法,分别定义 fast 和 slow 指针,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fast 和 slow指针在途中相遇 ,说明这个链表有环。

 此时已经可以判断链表是否有环了,那么接下来要找这个环的入口了。

我们假设假设从头结点到环形入口节点 的节点数为x。 环形入口节点到 fast指针与slow指针相遇节点 节点数为a。 从相遇节点 再到环形入口节点节点数为 b。 如图所示:

我们知道fast指针的速度是slow指针的两倍所以它们两个所走的 fast = 2 *slow

所以有:x+a+n(a+b) = 2*(x+a)  

化简可得: x = nb.

设n = 1 就可得 x = b

所以我们可以先当它们相遇后,让fast / slow 指向头结点,然后让它们以同时的速度,当它们再次相遇后既是环形入口点

代码可得

Java版本

public class Solution {
    public ListNode detectCycle(ListNode head) {
        if(head == null || head.next == null) {
            return null;
        }
        ListNode fast = head;
        ListNode slow = head;
        while(fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            if(fast == slow) break;
        } //没有环
        if(fast == null || fast.next == null) {
            return null;
        }
        fast = head;
        while(slow != fast) {
            slow = slow.next;
            fast = fast.next;
        } 
        return fast;
    }
}

C++版本

class Solution {
public:
    ListNode* detectCycle(ListNode* head) {
        if(head == nullptr || head->next == nullptr) {
            return nullptr;
        }
        ListNode* fast = head;
        ListNode* slow = head;
        while(fast != nullptr && fast->next != nullptr) {
            fast = fast->next->next;
            slow = slow->next;
            if(fast == slow) break;
        } // 没有环
        if(fast == nullptr || fast->next == nullptr) {
            return nullptr;
        }
        fast = head;
        while(slow != fast) {
            slow = slow->next;
            fast = fast->next;
        } 
        return fast;
    }
};

以上就是一些博主自己整理练习的面试题啦,如果对这一类博文感兴趣的话,可以关注博主哦,博主后面还会整理更多题目分享给大家 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1299772.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vite4、Vue3、Axios 针对请求模块化封装搭配自动化导入(简单易用)

针对请求模块化封装搭配自动化导入&#xff08;简单易用&#xff09; 目标目录目标代码前提步入正题src / utils / index.jssrc /api / index.jssrc /api / request.jssrc /api / service.jssrc /api / utils.jssrc /api / modules / demo.js 自动化配置vite.config.jseslint 校…

《PySpark大数据分析实战》-01.关于数据

&#x1f4cb; 博主简介 &#x1f496; 作者简介&#xff1a;大家好&#xff0c;我是wux_labs。&#x1f61c; 热衷于各种主流技术&#xff0c;热爱数据科学、机器学习、云计算、人工智能。 通过了TiDB数据库专员&#xff08;PCTA&#xff09;、TiDB数据库专家&#xff08;PCTP…

VSCode安装与使用

VS Code 安装及使用 1、下载 进入VS Code官网&#xff1a;地址&#xff0c;点击 DownLoad for Windows下载windows版本 注&#xff1a; Stable&#xff1a;稳定版Insiders&#xff1a;内测版 2、安装 双击安装包&#xff0c;选择我同意此协议&#xff0c;再点击下一步 选择你…

jquery实现省市区三级联动

一、技术: 前端采用的是jsp页面 后端采用springmvc+mybatis+mysql8 效果图 二、cascadeSelect.jsp页面 <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%String path = request.getContextPath();String basePath = r…

流程画布开发技术方案归档(G6)

&#x1f3a8; 在理想的最美好世界中&#xff0c;一切都是为最美好的目的而设。 —— 伏尔泰 如果可以实现记得点赞分享&#xff0c;谢谢老铁&#xff5e; 一、技术选型 •从可维护性和可拓展性出发 •基本满足 1&#xff1a;链接: https://github.com/hukaibaihu/vue-org…

Java 手写设计HashMap源码,让面试官膜拜

Java 手写HashMap源码&#xff0c;让面试官膜拜 一&#xff0c;手写源码 这是一个模仿HashMap的put&#xff0c;get功能的自定义的MyHashMap package cn.wxs.demo;import java.io.Serializable; import java.util.*; import java.util.function.BiConsumer;class MyHashMap&…

【解密考研英语:Python数据分析与可视化】

解密考研英语&#xff1a;Python数据分析与可视化 背景数据集技术选型功能实现创新点 大家好&#xff0c;欢迎阅读我的CSDN博客&#xff01;今天我将分享一项有关考研英语真题的数据分析与可视化项目&#xff0c;希望对考研学子提供更有针对性的复习帮助。 背景 作为考研学子…

【TwinCAT学习笔记 1】TwinCAT开发环境搭建

写在前面 作为技术开发人员&#xff0c;开启任何一项开发工作之前&#xff0c;首先都要搭建好开发环境&#xff0c;所谓磨刀不误砍材工&#xff0c;一定要有耐心&#xff0c;一次不行卸载再装。我曾遇到过一个学生&#xff0c;仅搭建环境就用了两周&#xff0c;这个过程也是一…

Docker容器的可视化管理工具—DockerUI本地部署与远程访问

文章目录 前言1. 安装部署DockerUI2. 安装cpolar内网穿透3. 配置DockerUI公网访问地址4. 公网远程访问DockerUI5. 固定DockerUI公网地址 前言 DockerUI是一个docker容器镜像的可视化图形化管理工具。DockerUI可以用来轻松构建、管理和维护docker环境。它是完全开源且免费的。基…

3接上篇 我的自定义GPTs的改进优化 与物理世界连接成功 GPTs的创建与使用定义和执行特定任务的功能模块 通过API与外部系统或服务的交互

https://blog.csdn.net/chenhao0568/article/details/134875067?spm1001.2014.3001.5502 从服务器日志里看到请求多了一个“location” 23.102.140.123 - - [08/Dec/2023:14:02:20 0800] "GET /getWeather.php?location&locationNewYork HTTP/1.1" 200 337 &…

公式识别任务各个链条全部打通

目录 引言公式识别任务是什么&#xff1f;公式识别任务解决方案初探使用建议写在最后 引言 随着LaTeX-OCR模型转换问题的解决&#xff0c;公式识别任务中各个链条已经全部打通。小伙伴们可以放开膀子干了。 解决业界问题的方案&#xff0c;并不是单独训练一个模型就完事了&am…

Spring Bean基础

写在最前面: 本文运行的示例在我github项目中的spring-bean模块&#xff0c;源码位置: spring-bean 前言 为什么要先掌握 Spring Bean 的基础知识&#xff1f; 我们知道 Spring 框架提供的一个最重要也是最核心的能力就是管理 Bean 实例。以下是其原因&#xff1a; 核心组件…

data_loader返回的每个batch的数据大小是怎么计算得到的?

data_loader是一个通用的术语&#xff0c;用于表示数据加载器或数据批次生成器。它是在机器学习和深度学习中常用的一个概念。 一、data loader 数据加载器&#xff08;data loader&#xff09;是一个用于加载和处理数据集的工具&#xff0c;它可以将数据集划分为小批次&#…

Oracle 中换行chr(10)、回车chr(13)

一、前言 chr(n)&#xff1a;返回 ascii 值对应的字符。 ascii(char)&#xff1a;返回字符 char对应的ascii 值。 chr(n) 和 ascii(char) 作用刚好是相反的。 SQL> select chr(65) from dual; 控制台显示&#xff1a;ASQL> select ascii(A) from dual; 控制台显示&am…

Oracle高可用一家老小全在这里

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是【IT邦德】&#xff0c;江湖人称jeames007&#xff0c;10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】&#xff01;&#x1f61c;&am…

FL Studio2024永久免费体验版下载

FL Studio中文绿色21版是一款无需要安装的汉化版本&#xff0c;它是一款非常专业的音频编辑软件&#xff0c;可以让你的音乐突破想象力的限制哦&#xff0c;FL Studio21中文版可以制作出不同音律的节奏&#xff0c;FL Studio内置众多电子合成音色&#xff0c;只Styrus可以让人激…

鸿蒙开发组件之ForEach列表

一、ForEach函数 ForEach函数是一个迭代函数&#xff0c;需要传递两个必须参数和一个可选参数。主要通过迭代来获取参数arr中的数据不断的生成单个Item来生成鸿蒙中的列表样式 二、先创建单个的Item的UI 通过嵌套Row与Column来实现单个Item的UI。例如图中没有折扣的可以看成一…

C++-引用和指针区别

文章目录 1.变量的组成2.指针2.1 定义2.2 使用指针操作变量2.3 为什么使用指针 3.引用3.1 定义3.2 引用注意事项 4.引用和指针的区别 1.变量的组成 变量的组成&#xff1a;变量地址&#xff0c;变量名&#xff0c;变量值 例&#xff1a; int i 12;2.指针 2.1 定义 指针用于存…

【Python】简单的翻译软件

用translate包和tkinter写一个简单的桌面翻译软件。 1、窗口设置&引入包&#xff1a; from tkinter import * from tkinter.ttk import * from tkinter.messagebox import * import translatewinTk() win.title(翻译) win.geometry("600x400")win.mainloop() …

【Linux系统编程】初步运用git工具

介绍&#xff1a; 使用git之前首先要先认识gitee/github&#xff0c;gitee/github是一个远程仓库网站。git是平台专门开发的一个操控工具&#xff0c;是一个开源的分布式版本控制系统&#xff0c;我们使用git工具来与gitee/github来取得联系。 git的推送使用&#xff1a; git既…