05 |「链表」必刷题

news2024/9/20 16:39:06

前言

前言:刷链表面试高频题。

文章目录

    • 前言
    • 一. 基础回顾
    • 二. 高频考题
      • 1、例题
        • 例题1:LeetCode 206 反转链表
          • 1)题目链接
          • 2) 算法思路
          • 3)源码剖析
          • 4)时间复杂度
        • 例题2:LeetCode 92 反转链表II
          • 1)题目链接
          • 2) 算法思路
          • 3)源码剖析
          • 4)时间复杂度
        • 例题3:LeetCode 203 移除链表元素
          • 1)题目链接
          • 2)遍历做法
          • 3)递归做法
          • 3)时间复杂度
      • 2、习题
        • 习题1:LeetCode 19 删除链表的第N个节点
          • 1)题目链接
          • 2) 算法思路
          • 3)源码剖析
          • 3)时间复杂度
        • 习题2:LeetCode 876 链表的中间节点
          • 1)题目链接
          • 2) 算法思路
          • 3)源码剖析
          • 3)时间复杂度
        • 习题3:LeetCode 160 相交链表
          • 1)题目链接
          • 2) 算法思路
          • 3)源码剖析
          • 3)时间复杂度
        • 习题4:LeetCode 141 环型链表
          • 1)题目链接
          • 2) 算法思路
          • 3)源码剖析
          • 4)时间复杂度
        • 习题5:LeetCode 142. 环形链表 II
          • 1)题目链接

一. 基础回顾

参考上一讲: 04 |「链表」简析

结构 1 1 1-> 2 2 2-> 3 3 3->NULL,链表是 指向型结构 。
查找:随机访问的时间复杂度是 O ( n ) O(n) O(n)
增删:删除和插入元素的时间复杂度都是 O ( 1 ) O(1) O(1)

头结点(head:对于链表,给你一个链表时,我们拿到的是头节点(head) 。如果没有头结点证明整个链表为空 NULL,如果已经有头结点证明链表不为空。

虚拟头结点(dummy :针对链表,需要经常判断头结点 (head)头结点为空和不为空对应不同的操作,增加哨兵节点统一操作。如果链表为空(head = null),那么 访问 null.valnull.next 出错。为了避免这种情况,增加一个虚拟头结点(dummy),其中 dummy 的值 (val)常用 -1 表示,这样 dummy.next = null,避免直接访问空指针。

// 增加虚拟头节点的链表遍历
dummy; dumm->next = head; p = dummy;
while (p) 
{

}
// 没有虚拟头结点的链表遍历
head;
while (head)
{
    head = head->next;
}

二. 高频考题

1、例题

例题1:LeetCode 206 反转链表

1)题目链接

原题链接:反转链表(点击链接直达)

2) 算法思路
  • 明确:修改几条边,修改哪几条边,注意是修改 n 条边;
  • 操作:将当前节点的 next 指针改为指向前一个节点(last);
  • 维护:双链表可以通过 pre 指针访问前一个节点。针对单链表,没有 pre 指针无法访问前一个节点(last),需要新开一个变量维护前一个节点(last);
  • 边界:针对头结点(head)没有前一个节点,创建 last 并赋为 NULL

在这里插入图片描述

3)源码剖析
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
    	if (!head || !head->next) return head;
        ListNode* last = nullptr;			//(1)
        ListNode* cur = head;				//(2)
        while (cur)							//(3)
        {
            ListNode* next = cur->next;		//(4)
            cur->next = last;				//(5)
            last = cur;						//(6)
            cur = next;						//(7)
        }
        return last;						//(8)
    }
};
  • (1)/(2):初始化变量 lastcurlast 指向上一个节点,cur 指向当前节点;
  • (3):修改每条边,需要循环遍历访问每个节点;
  • (4):修改一条边时,先保存当前节点(cur)的下一个节点(next),防止丢失;
  • (5):修改一条边;
  • (6)/(7):lastcur 分别向后移动一位;
  • (8):返回反转后链表的头结点。当 cur 停下时指向原链表的 NULL,此时 last 指向反转后链表的头结点;
4)时间复杂度

         O ( n ) O(n) O(n)


例题2:LeetCode 92 反转链表II

1)题目链接

原题链接:反转链表(点击链接直达)

2) 算法思路
  • tmp 节点移动到 left-1 的位置处;
  • 反转 [left, right] 部分的节点。从 left 位置开始反转,反转 right-left 次;
  • 调整剩余部分节点的指向;
  • 返回头结点;
3)源码剖析
class Solution {
public:
    ListNode* reverseBetween(ListNode* head, int left, int right) {
    	if (left == right) return head;							//(1)
        ListNode* dummy = new ListNode(-1);						//(2)
        dummy->next = head;
        ListNode* tmp = dummy;
        for (int i = 0; i < left - 1; i ++) tmp = tmp->next;	//(3)
        														//(4)						
        ListNode* pre = tmp->next;
        ListNode* cur = pre->next;
        for (int i = 0; i < right - left; i ++) 
        {
            ListNode* next = cur->next;
            cur->next = pre;
            pre = cur;
            cur = next;
        }
        														//(5)											
        tmp->next->next = cur;
        tmp->next = pre;

        return dummy->next;										//(6)
    }
};
  • (1):left=right 证明只有一个头结点;
  • (2):dummy 为哨兵节点。因为 left 可能在 head 位置,故添加哨兵节点;
  • (3):将 tmp 节点移动到 left-1 的位置;
  • (4):(4)- (5)之间的代码为反转 [left, right] 部分的节点,逻辑同上题;
  • (5):(5)-(6) 之间的代码为调整其它节点的指向。如示例12next 指向 51next 指向 4
  • (6):返回链表头节点;
4)时间复杂度

         O ( n ) O(n) O(n)


例题3:LeetCode 203 移除链表元素

1)题目链接

原题链接:移除链表元素(点击链接直达)

2)遍历做法
  • 增加 dummy 哨兵节点的目的是统一操作,少写特判断头结点(head)是否为空。
    // 不增加哨兵节点dummy
    if (!head) {
    	return head;
    } else {
    
    }
    
    // 增加哨兵节点dummy
    class Solution {
    public:
        ListNode* removeElements(ListNode* head, int val) {
            ListNode* dummy = new ListNode(-1);
            dummy->next = head;
            ListNode* p = dummy;
    
            while (p->next)
            {
                if (p->next->val == val) p->next = p->next->next;
                else p = p->next;
            }
    
            return dummy->next;
        }
    };
    
3)递归做法
if (!head) return head;
head->next = removeElements(head->next, val); 
return head->val == val? head->next : head;
3)时间复杂度

         O ( n ) O(n) O(n)


2、习题

习题1:LeetCode 19 删除链表的第N个节点

1)题目链接

原题链接: 删除链表的第N个节点(点击链接直达)

2) 算法思路

纸上画图实际模拟一遍即可。

3)源码剖析
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dummy = new ListNode(-1);			//(1)
        dummy->next = head;
        ListNode* p = dummy, *q = dummy;
        for (int i = 0; i < n; i ++) p = p->next;	//(2)
        while (p->next != nullptr) 					//(3)
        {
            p = p->next;
            q = q->next;
        }
        q->next = q->next->next;					//(4)

        return dummy->next;
    }
};
  • (1):定义虚拟头结点 dummy,不用考虑头结点的特殊情况;
  • (2):p 指针先走 n 步;
  • (3):p 指针和 q 指针同时走,直到 p 指针走到最后一个节点,两指针都停下;
  • (4):此时 q 指向的就是要删除节点的前一个节点(n-1处),删除第 n 个节点;
3)时间复杂度

        双指针遍历时间复杂度为 O ( n ) O(n) O(n)


习题2:LeetCode 876 链表的中间节点

1)题目链接

原题链接: 链表的中间节点(点击链接直达)

2) 算法思路
  • 模拟枚举。奇数个节点, q 走到中点时,p->nextNULL。偶数个节点,q 走到中点时,fast为空 NULL
3)源码剖析
class Solution {
public:
    ListNode* middleNode(ListNode* head) {
        auto p = head, q = head;

        while (p && p->next) {  // 只要p和p->next都不为空时,两指针就一种往后走
            p = p->next->next;
            q = q->next;
        }
        return q;
    }
};
3)时间复杂度

        双指针遍历时间复杂度为 O ( n ) O(n) O(n)


习题3:LeetCode 160 相交链表

1)题目链接

原题链接: 相交链表(点击链接直达)

2) 算法思路
  • 判断相交:两指针是否相等;
  • 难点:两个链表相同节点前面的长度不同,无法控制遍历的长度。
    例如,链表 a1=>2=>3=>4,链表 b5=>3=>4,相同节点为 33 前面的链表部分长度两个不相等;
  • 解决:将两个链表逻辑上拼接在一起。先遍历链表 a,遍历完后再遍历链表 b。同理,先先遍历链表 b,遍历完后再遍历链表 a。这样,相同节点前面的长度就保持一致了,可以通过遍历相同的次数走到相同的节点;
    例如,链表 a 逻辑上变为:1=>2=>3=>4=>5=>3=>4,链表 b 逻辑上变为:5=>3=>4=>1=>2=>3=>4
3)源码剖析
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        auto p = headA, q = headB;
        while (p != q) {
        	// p没走到A链表终点就一直往后走,走到终点就开始走B链表
            p = p != NULL? p->next : headB;
            // q没走到B链表终点就一直往后走,走到终点就开始走A链表
            q = q != NULL? q->next : headA;
        }
        return p;
    }
};
3)时间复杂度

         O ( n ) O(n) O(n)


习题4:LeetCode 141 环型链表

1)题目链接

原题链接: 环型链表(点击链接直达)

2) 算法思路
  • 明确什么叫有环;
  • 明确有环和无环的区别:
    • 定义:fast 是跑得快的指针,slow是跑的慢的指针。快指针每次走两步,慢指针每次走一步;
    • 有环:有环相当于 fastslow 两指针在环形操场跑,如果 fastslow 相遇,那一定是 fast 超过了 slow 一圈;
    • 无环:无环相当于 fastslow 两指针在直道操场跑,因为快指针跑的快会先达到终点,则两指针一定不会遇到;
3)源码剖析
class Solution {
public:
    bool hasCycle(ListNode *head) {
        ListNode* slow = head, *fast = head;

        while (fast && fast->next)			//(1)
        {
            fast = fast->next->next;		//(2)
            slow = slow->next;				//(3)
            if (fast == slow) return true;	//(4)
        }
        return false;						//(5)
    }
};
  • (1):判断快指针是否到达终点;
  • (2):快指针每次走两步;
  • (3):慢指针每次走一步;
  • (4):两指针相遇,证明两指针套圈了,则一定有环;
  • (5):快指针先达到终点,证明无环;
4)时间复杂度

         O ( n ) O(n) O(n)


习题5:LeetCode 142. 环形链表 II

1)题目链接

原题链接: 环形链表 II(点击链接直达)

LeetCode 234 回文链表 原题链接

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

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

相关文章

C++编译之(3)-camke/CMakeLists.txt的编译使用教程

引言 上一节介绍了前面我们介绍了make/Makefile来对c项目进行编译&#xff0c;我们继续以该项目为例讲解&#xff1b; C编译之(1)-g单/多文件/库的编译 C编译之(2)-make及makefile编译过程 我们先看看上一节的实战的目录结构如下&#xff1a; - mutilFilesDemo- include // 头…

Docker入门与应用

Docker入门与应用1.初识Docker1.1.什么是Docker1.1.1.应用部署的环境问题1.1.2.Docker解决依赖兼容问题1.1.3.Docker解决操作系统环境差异1.1.4.小结1.2.Docker和虚拟机的区别1.3.Docker架构1.3.1.镜像和容器1.3.2.DockerHub1.3.3.Docker架构1.3.4.小结1.4.安装Docker2.Docker的…

“华为杯”研究生数学建模竞赛2005年-【华为杯】D题:仓库容量有限条件下的随机存贮管理(附获奖论文)

赛题描述 工厂生产需定期地定购各种原料,商家销售要成批地购进各种商品。无论是原料或商品,都有一个怎样存贮的问题。存得少了无法满足需求,影响利润;存得太多,存贮费用就高。因此说存贮管理是降低成本、提高经济效益的有效途径和方法。 问题2 以下是来自某个大型超市的…

全链路追踪 jaeger

Jaeger 概述 Jaeger 是 Uber 开发并开源的一款分布式追踪系统&#xff0c;兼容 OpenTracing API&#xff0c;适用于以下下场景&#xff1a; 分布式跟踪信息传递分布式事务监控问题分析服务依赖性分析性能优化 特性 高扩展性 Jaeger后端的设计没有单点故障&#xff0c;可以…

大数据 | 《Riffle:Optimized Shuffle Service for Large-Scale》论文阅读

1. 简介 1.1. 近期工作 研究工作鼓励运行大量小任务 小任务能提高并行性&#xff0c;减少端到端耗时工程经验反对运行过多的任务 过多的task在shuffle阶段会引入大量IO开销&#xff0c;根本原因在于map和reduce阶段之间的shuffle IO请求数量随着任务数量的增长呈现指数级的增…

emoji 符号大全,给各位程序员增加一些奇怪的知识点

这篇博客非常有意思&#xff0c;我将为大家整理和罗列一些好用的 emoji 表情站点。 文章目录EmojiXDcarpedm20emoji-cheat-sheetemojiterrafsymbols符号大全unicode.orgemojiallemojiguideemojipediaemoji696 编辑器emoji.inkEmoji Artemojifinderemoji 可以在许多社交媒体平台…

Redis 持久化-AOF

Redis 持久化-AOF 1.官方资料 在线文档 : https://redis.io/topics/persistence 2.AOF 是什么? 1、AOF(Append Only File) 2、以日志的形式来记录每个写操作(增量保存)&#xff0c;将 Redis 执行过的所有写指令记录下来(比 如 set/del 操作会记录, 读操作 get 不记录) 3、…

【大数据趋势】1月24日 美元关键位置上,应该不会一次破,纳指有概率反弹,人民币结汇行情结束在即。

确定市场形态 - 美元指数 关键位置大概率不会一次就破&#xff0c;有较强反弹 作为长期的关键位置101-103这个区域&#xff0c;没有可能一次性涨破&#xff0c;或者一次性跌破&#xff0c;所以大概率有一次反弹出现。作为趋势线&#xff08;红色&#xff09;来看&#xff0c…

十大经典排序算法(动态演示+代码)-冒泡算法

时间、空间复杂度比较 排序算法平均时间复杂度最差时间复杂度空间复杂度数据对象稳定性冒泡排序O(n2)O(n2)O(1)稳定选择排序O(n2)O(n2)O(1)数组不稳定、链表稳定插入排序O(n2)O(n2)O(1)稳定快速排序O(n*log2n)O(n2)O(log2n)不稳定堆排序O(n*log2n)O(n*log2n)O(1)不稳定归并排序…

万能四码(0126版本)之分析

万能四码&#xff08;0126版本&#xff09;之分析一、万能四码的重新排列原版是这样的&#xff1a;0126&#xff0c;0134&#xff0c;0159&#xff0c;0178&#xff0c;0239&#xff0c;0247&#xff0c;0258&#xff0c;0357&#xff0c;0368&#xff0c;0456&#xff0c;0489…

【进阶C语言】程序环境与预处理

文章目录一.程序环境1.翻译环境编译器1.预处理2.编译3.汇编链接器2.运行环境总图解二.预处理1.预定义符号2.define1.define的定义2.替换规则3.定义的建议和使用的缺点1.加括号2.避免使用带有副作用的符号3.命名约定4.#和##1.#2.##5.宏和函数的对比6.undef3.条件编译1.常量表达式…

趣味三角——第3章——6个三角函数的成熟过程

目录 3.1 6个三角函数的演化进程简述 3.2 Johann Muller(别名Regiomontanus)的贡献 第3章 6个三角函数的成熟过程 It is quite difficult to describe with certainty the beginning of trigonometry . . . . In general, one may say that the emphasis was placed first …

ThinkPHP5 Request类method任意方法调用RCE

ThinkPHP v5.0.x 影响版本&#xff1a;5.0.0 < version < 5.0.23 漏洞点&#xff1a;\think\Request::method 修复&#xff1a;版本更新 top-think/framework4a4b5e6 改进Request类 环境 thinkphp5.0.23核心版&#xff08;需开启debug&#xff09;thinkphp5.0.22完…

设计模式 - 六大设计原则之LoD(迪米特法则原则)

文章目录概述Case学生类老师类Bad ImplBetter Impl老师类改造调用方&#xff1a;校长类改造概述 迪米特法&#xff08;Law Of Demeter &#xff0c; LoD&#xff09;则又叫最少知道原则&#xff08;Least Knowledge Principle&#xff09;&#xff0c;最早是在1987年由美国Nor…

【游戏客户端】如何实现环形进度条

【游戏客户端】如何实现环形进度条 Hello大家好&#xff0c;我是Lampard。好久没写博客了&#xff0c;之前在忙着制作项目的一个大的副本&#xff0c;趁着过年得闲&#xff0c;和大家分享一下制作过程中遇到的一些有趣的问题。今天主要是分享如何在cocos制作一个环形的进度条 (…

重写 equals 时为什么一定要重写 hashCode

equals 方法和 hashCode 方法是 Object 类中的两个基础方法&#xff0c;它们共同协作来判断两个对象是否相等。为什么要这样设计嘞&#xff1f;原因就出在“性能” 2 字上。 使用过 HashMap 我们就知道&#xff0c;通过 hash 计算之后&#xff0c;我们就可以直接定位出某个值存…

移动web 空间转换 3D

移动web 空间转换 3D空间转换 3D3D位移透视3D旋rotateXrotateY左手法则立体呈现空间转换 3D 3D坐标系 3D 坐标系比2D 多了一个Z轴。 一定要记住3个坐标轴取值的正反&#xff1a; X 轴 往右越大&#xff0c;是正值&#xff0c; 否则反之Y 轴 往下越大&#xff0c;是正值&…

React错误边界

首先 我们先构建出问题的场景 我们创建一个react项目 然后在src下创建 components 文件夹目录 在下面创建一个 error.jsx 组件 参开代码如下 import React from "react";export default class App extends React.Component{constructor(props){super(props);this.…

CUDA编程笔记(5)

文章目录前言CUDA的内存组织全局内存常量内存纹理内存和表面内存寄存器局部内存共享内存L1和L2缓存SM的构成API函数查询设备总结前言 cuda的内存组织&#xff0c;在使用GPU时尽可能提高性能&#xff0c;合理的使用设备的内存也是十分重要的。 CUDA的内存组织 如表所示&#…

Docker基本操作

Docker基本操作一、镜像操作1.镜像名称2.镜像命令&#xff08;1&#xff09;拉取、查看镜像&#xff08;2&#xff09;保存、导入镜像二、容器操作1.容器相关命令2.创建并运行一个容器3.进入容器&#xff0c;修改文件4.小结三、数据卷&#xff08;容器数据管理&#xff09;1.什…