【LeetCode 算法专题突破】链表(⭐)

news2024/9/23 9:23:16

文章目录

  • 前言
  • 1. 移除链表元素
    • 题目描述
    • 代码
  • 2. 设计链表
    • 题目描述
    • 代码
  • 3. 反转链表
    • 题目描述
    • 代码
  • 4. 两两交换链表中的节点
    • 题目描述
    • 代码
  • 5. 删除链表的倒数第 N 个结点
    • 题目描述
    • 代码
  • 6. 链表相交
    • 题目描述
    • 代码
  • 7. 环形链表 II
    • 题目描述
    • 代码
  • 总结

前言

链表题目一向是面试算法考察的一个热点,作为一个必刷的专题,早做晚做都得做,不如早点将这个专题拿下~

1. 移除链表元素

刷链表的题目,那当然是从最经典,也是最基础的移除链表元素开始啦~

题目链接:203. 移除链表元素

题目描述


思路没什么好说的,就是遍历链表然后移除指定节点即可,来看代码:

代码

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
func removeElements(head *ListNode, val int) *ListNode {
    phead := &ListNode{Next: head} // 创建哨兵位
    cur := phead
    for cur.Next != nil {
        tmp := cur
        cur = cur.Next // 遍历
        if cur.Val == val { // 移除节点的操作
            tmp.Next = cur.Next
            cur = tmp
        }
    }
    return phead.Next
}

有两个需要注意的地方:

  1. 我这里创建了一个哨兵位,这样可以让我们在移除元素的时候更方便,不需要对头插进行特判(我个人比较喜欢添加哨兵位)
  2. 移除节点的操作细节(推荐画图分析链表题目,再根据自己画的图写代码,思路会非常的清晰)

2. 设计链表

这道题目其实并不算是一道算法题,他训练的是我们的链表的基本功

题目链接:707. 设计链表

题目描述


这道题也没什么可说的,按照题目把需要实现的接口都实现了就行,训练基本功的时候总是非常痛苦的,不痛苦又怎么能够进步呢~
来看代码:

代码

type MyLinkedList struct {
    head *ListNode
    size int
}

func Constructor() MyLinkedList {
    return MyLinkedList{&ListNode{}, 0}
}

func (list *MyLinkedList) Get(index int) int {
    if index < 0 || index >= list.size {
        return -1
    }
    cur := list.head
    for i := 0; i <= index; i++ {
        cur = cur.Next
    }
    return cur.Val
}

func (list *MyLinkedList) AddAtHead(val int)  {
    list.AddAtIndex(0, val)
}

func (list *MyLinkedList) AddAtTail(val int)  {
    list.AddAtIndex(list.size, val)
}

func (list *MyLinkedList) AddAtIndex(index int, val int)  {
    if index > list.size {
        return
    }
    index = max(index, 0)
    cur := list.head
    for i:= 0; i < index; i++ {
        cur = cur.Next
    }
    newnode := &ListNode{val, cur.Next}
    cur.Next = newnode
    list.size++
}

func (list *MyLinkedList) DeleteAtIndex(index int)  {
    if index < 0 || index >= list.size {
        return
    }
    cur := list.head
    for i:= 0; i < index; i++ {
        cur = cur.Next
    }
    cur.Next = cur.Next.Next
    list.size--
}

func max(a, b int) int {
    if a > b {
        return a
    }
    return b
}

/**
 * Your MyLinkedList object will be instantiated and called as such:
 * obj := Constructor();
 * param_1 := obj.Get(index);
 * obj.AddAtHead(val);
 * obj.AddAtTail(val);
 * obj.AddAtIndex(index,val);
 * obj.DeleteAtIndex(index);
 */

这道题目没有什么非常需要注意的地方,插入和删除的操作也相对简单,不要忘记判断传过来的参数是否合法就行,唯一需要注意的地方是,我们可以不用实现头插和尾插的操作,只需要实现插入的操作,然后直接进行复用就行,复用的思想无论到哪里都不过时~

3. 反转链表

接下来这道题目更是重量级,也许你前面或者后面的链表题目都没做过,但至少也会见过这道题,永远的经典,反转链表。这里分享一个小故事:我在刚学链表的时候,我的数据结构老师跟我说,他当年找工作面试学生的时候就被考过这道题目,现在他教的一个学生出去面试了,面试官也出了这道题目,这何尝不是一种传承呢~

题目链接:206. 反转链表

题目描述


这道题目的解法其实不少,但是我最喜欢,也是我用的最多的一种方法是通过反转指针来完成链表的反转。来看代码:

代码

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
func reverseList(head *ListNode) *ListNode {
    var prev *ListNode = nil
    var cur *ListNode = head
    for cur != nil {
        tmp := cur.Next
        cur.Next = prev
        prev = cur
        cur = tmp
    }
    return prev
}

这道题主要就是需要一个 prev 的辅助指针来帮助我们反转链表的指针指向,在之后许多链表相关的题目我们常常会使用一些辅助的指针来帮助我们操作。

另外,在做这道题题目的时候是不能用 Golang 的语法糖来初始化 prev 指针的:prev := &ListNode{},因为 := 符号没有办法初始化出 nil 值,所以只好使用 var 来定义了。

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

我们继续来练习~

题目链接:24. 两两交换链表中的节点

题目描述


这道题目可以用递归来做代码会相对少一点,不过我习惯使用迭代来做链表的题目,所以我选择的还是通过迭代来对链表进行操作,我个人觉得迭代的方法也会比递归要更容易去理解。来看代码:

代码

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
func swapPairs(head *ListNode) *ListNode {
    phead := &ListNode{Next: head}
    cur := phead
    for cur.Next != nil && cur.Next.Next != nil {
        node1 := cur.Next
        node2 := cur.Next.Next

        // 这个的主要作用有两个:
        // 1. 第一次的时候让哨兵位指向第一个节点
        // 2. 之后每次的翻转后连接下一段节点
        cur.Next = node2

        // 交换位置
        node1.Next = node2.Next
        node2.Next = node1

        cur = node1  
    }
    return phead.Next
}

链表题最核心的地方其实就是具体操作链表时的逻辑,这里我采用的是让 cur 一直处于一个哨兵位的状态,通过设置 node1 和 node2 来进行互换的操作,每次交换的连接操作就是这段代码的核心:cur.Next = node2。

也许我们做第一遍的时候想不出解决的方案,但是当我们刷第二遍,第三遍,刷更多更多的题目之后,再遇到类似的题目我们也不会怕了。

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

我们再来刷一道经典链表题

题目链接:19. 删除链表的倒数第 N 个结点

题目描述


这道题直接做其实并不难,而他经典的地方就在于,怎么样能够是实现一次遍历完成题目的要求,也就是 LeetCode 这道题最后那一句进阶做法,这个就是这道题思路的精华。来看代码:

代码

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
func removeNthFromEnd(head *ListNode, n int) *ListNode {
    phead := &ListNode{Next: head}
    slow, fast := phead, head
    for n > 0 { // 让 fast 指针先走 n 步
        fast = fast.Next
        n--
    }
    for fast != nil { // 同时遍历,这样 slow 指针就会离链表尾 n 个身位
        slow = slow.Next
        fast = fast.Next
    }
    slow.Next = slow.Next.Next // 删除节点
    return phead.Next
}

这道题想要做到一次遍历完成,就得用到双指针的思想(这也是我们最开始先把双指针学了的原因之一)链表使用双指针的思想来解题其实并不罕见,这里就是一个简单的快慢指针的应用,我们看代码的注释可以很容易的把核心思路给看懂。

6. 链表相交

我们再来刷一道经典的链表题目:

题目链接:面试题 02.07. 链表相交

题目描述


LeetCode 这里把题目给的很长很长,其实我也没有吧题目全部看完,其实抓住核心点就行了,这道题目的意思就是让我找出两个链表相交的点,然后返回那个点就行,如果没哟相交的点就返回空。来看代码:

代码

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
func getIntersectionNode(headA, headB *ListNode) *ListNode {
    if headA == nil || headB == nil { // 如果有一个链表是空,那肯定没相交
        return nil
    }
    curA, curB := headA, headB
    for curA != nil || curB != nil { // 互相走一遍对方的路
        if curA == nil {
            curA = headB
        }
        if curB == nil {
            curB = headA
        }
        if curA == curB { // 相交的位置
            return curA
        }
        curA = curA.Next
        curB = curB.Next
    }
    return nil
}   

如果是直接做的话,有很多的方法都能找到他们相交的地方,所以题目有一个进阶的要求,让我们用 O(N) 的时间,O(1) 的空间复杂度解决这道题目,实际上我第一次做的时候也是想不出来这能怎么实现的,看了题解才恍然大悟,非常的巧妙

具体的思路是这样的,我们设置两个指针遍历两个链表,指针走完自己的链表之后(也就是走到 nil 之后)到另外一个链表重头开始遍历,如果两个链表存在相交,那两个指针就会相遇,如果没有相交,那他们就会同时走到 nil,这样返回 nil 即可。

7. 环形链表 II

接着就是最后一道,非常经典的环形链表

题目链接:142. 环形链表 II

题目描述


如果这道题目想按照题目要求的那样,使用 O(1) 的空间,用双指针来做的话,更像是一个数学题,来看代码:

代码

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
func detectCycle(head *ListNode) *ListNode {
    slow, fast := head, head
    for {
        if fast == nil || fast.Next == nil { // 走到尾了,证明没环
            return nil
        }
        slow = slow.Next
        fast = fast.Next.Next
        if slow == fast { // 相遇了,证明有环
            break
        }
    }
    fast = head // 让 fast 回头重新遍历
    for slow != fast {
        slow = slow.Next
        fast = fast.Next
    }
    return fast
}

所以这道题的重点就在最后一步为什么要这么做,我这里把证明贴出来:

  1. 指针走过的步数为 a + nb 时可重返环入口(如果链表有环)
  2. 设在双指针在第一次相遇时 slow 的步数 s 为 s 步, 那么 fast 的步数f即为 2s 步
  3. 在环中相遇时, fast 比 slow 多走 nb 步,也即 fast 走了 s + nb 步
  4. 结合条件 2,3 :f = 2nb, s = nb
  5. 结合条件 1 与结论 1: slow 再走 a 步即可到达入口点

总结

链表的题目其实还有很多很多,这里就把一些经典的题型给总结到了一起,如果什么时候对链表生疏了,来刷几道,感觉一定能很快回来~

至于其他的一些变式题目,基础打牢,怕什么变式题呢~,题目永远是刷不完的,多做多总结,才能越做越顺手。

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

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

相关文章

【2024秋招】万得后端开发java 2023-7-13 2.30pm 一二面面经(附答案)

一面&#xff1a;20min 1 自我介绍 2 微服务架构 1 nacos作为配置中心&#xff0c;如果nacos服务失效了&#xff0c;各个服务之间的调用如何保持高可用呢&#xff1f; 答&#xff1a;nacos注册中心本地有缓存&#xff0c;所以请求来了还是能够正常提供一段时间的服务&#xff…

c语言进制的转换2进制转换8进制

c语言进制的转换之2进制转换8进制 c语言的进制的转换 c语言进制的转换之2进制转换8进制一、八四二一法则二、二进制转换八进制方法 一、八四二一法则 二、二进制转换八进制方法 如&#xff1a;111000110101001转换成八进制 按照八四二一法则 将二进制3个一等分变成&#xff1a…

LVS集群-DR模式【部署高可用LVS-DR集群】

文章目录 2.2 实战&#xff1a;配置LVS-DR集群2.2.1 配置IP2.2.2 生成ens33:1配置文件2.2.3 配置LVS-DR规则2.2.4 两台RealServer的IP配置Alastor62&#xff08;配置IP&#xff1a;192.168.1.62&#xff09;Alastor64&#xff08;配置IP&#xff1a;192.168.1.64&#xff09;客…

Macos视频增强修复工具:Topaz Video AI for mac

Topaz Video AI是一款使用人工智能技术对视频进行增强和修复的软件。它可以自动降噪、去除锐化、减少压缩失真、提高清晰度等等。Topaz Video AI可以处理各种类型的视频&#xff0c;包括低分辨率视频、老旧影片、手机录制的视频等等。 使用Topaz Video AI非常简单&#xff0c;…

C++设计模式_13_Flyweight享元模式

Flyweight享元模式仍然属于“对象性能”模式。 文章目录 1. 动机(Motivation)2. 模式定义3. 结构( Structure)4. 代码演示5. 要点总结6. 其他参考 1. 动机(Motivation) 在软件系统采用纯粹对象方案的问题在于大量细粒度的对象会很快充斥在系统中&#xff0c;从而带来很高的运行…

Web攻防05_MySQL_二次注入堆叠注入带外注入

文章目录 MYSQL-二次注入-74CMS思路描述&#xff1a;注入条件&#xff1a;案例&#xff1a;74CMS个人中心简历功能 MYSQL-堆叠注入-CTF强网思路描述注入条件案例&#xff1a;2019强网杯-随便注&#xff08;CTF题型&#xff09; MYSQL-带外注入-DNSLOG注入条件使用平台带外应用场…

代碼隨想錄算法訓練營|第四十九天|139.单词拆分、关于多重背包、背包问题总结。刷题心得(c++)

目录 讀題 139.单词拆分 自己看到题目的第一想法 看完代码随想录之后的想法 139.单词拆分 - 實作 思路 Code 關於多重背包 與01背包與完全背包的差別 轉化成01背包問題 背包问题总结 背包問題分類 背包問題 - 遞推公式 最多裝多少/能否裝滿 最大價值 裝滿背包有…

OpenFeign实现分析、源码解析

什么是openfeign? 是springcloud全家桶的组件之一&#xff0c;其核心作用是为Rest API提供高效简洁的rpc调用方式。 为什么只定义接口而没有实现类&#xff1f; 源码解读&#xff08;省略&#xff09; 总结&#xff1a; 源码分析&#xff1a;如何发送http请求&#xff1f; …

基于单片机设计的智能窗帘控制系统

一、前言 智能家居技术在近年来取得了巨大的发展&#xff0c;并逐渐成为人们日常生活中的一部分。智能家居系统带来了便利、舒适和高效的生活体验&#xff0c;拥有广泛的应用领域&#xff0c;其中之一就是智能窗帘控制系统。 传统窗帘需要手动操作&#xff0c;打开或关闭窗帘…

微服务-Ribbon负载均衡

文章目录 负载均衡原理流程原理源码分析负载均衡流程 负载均衡策略饥饿加载总结 负载均衡原理 流程 原理 LoadBalanced 标记RestTemplate发起的http请求要被Ribbon进行拦截和处理 源码分析 ctrlshiftN搜索LoadBalancerInterceptor&#xff0c;进入。发现实现了ClientHttpRequ…

Snipaste--强大的截图贴图软件--非常实用

一.软件介绍&#xff1a; Snipaste 是一个简单但强大的截图工具&#xff0c;也可以让你将截图贴回到屏幕上&#xff01;下载并打开Snipaste&#xff0c;按下 F1 来开始截图&#xff0c;再按 F3&#xff0c;截图就在桌面置顶显示了。就这么简单&#xff01;你还可以将剪贴板里的…

SK-Net eca注意力机制应用于ResNet (附代码)

resnet发展历程 论文地址&#xff1a;https://arxiv.org/pdf/1903.06586.pdf 代码地址&#xff1a;https://github.com/pppLang/SKNet 1.是什么&#xff1f; SK-net网络是一种增加模块嵌入到一些网络中的注意力机制&#xff0c;它可以嵌入和Resnet中进行补强&#xff0c;嵌入…

composer安装thinkphp6报错

composer安装thinkphp6报错&#xff0c; 查看是否安装了对应的PHP扩展&#xff0c;我这边使用的是宝塔的环境&#xff0c;全程可以可视化操作 这样就可以安装完成了

linux工具篇

文章目录 linux工具篇1. linux 软件包管理器-yum1.1 什么是软件包1.2 yum的使用1.3 yum源 2. linux编辑器-vim2.1 vim概念2.2 vim各个模式切换2.3 vim正常模式命令汇总2.4 vim底行模式各命令汇总2.5 vim的简单配置 3. Linux编译器-gcc/g使用3.1 复习程序编译过程(1) 预处理(2) …

【Oracle】Navicat Premium 连接 Oracle的两种方式

Navicat Premium 使用版本说明 Navicat Premium 版本 11.2.16 (64-bit) 一、配置OCI 1.1 配置OCI环境变量 1.1.2 设置\高级系统设置 1.1.2 系统属性\高级\环境变量(N) 1.1.3 修改/添加系统变量 ORACLE_HOME ORACLE_HOME D:\app\root\product\12.1.0\dbhome_11.1.4 添加系…

Redis快速上手篇(一)(安装与配置)

NoSQL NoSQL 是 Not Only SQL 的缩写&#xff0c;意即"不仅仅是 SQL"的意思&#xff0c;泛指非关系型的数据库。强调 Key-Value Stores 和文档数据库的优点。 NoSQL 产品是传统关系型数据库的功能阉割版本&#xff0c;通过减少用不到或很少用的功能&#xff0c;来大…

vue3中使用svg并封装成组件

打包svg地图 安装插件 yarn add vite-plugin-svg-icons -D # or npm i vite-plugin-svg-icons -D # or pnpm install vite-plugin-svg-icons -D使用插件 vite.config.ts import { VantResolver } from unplugin-vue-components/resolvers import { createSvgIconsPlugin } from…

【C语言初阶】switch语句的基本语法

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏:《速学C语言》《数据结构篇》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 前言&#x1f4ac; switch语句的介绍&#x1f4ac; switch语句的语法形式&#x1f4ad; 在switch语句中的 break&…

景联文科技提供4D-BEV标注工具:提升自动驾驶感知能力的精准数据支持

4D-BEV标注是一种用于自动驾驶领域的数据标注方法。在3D空间的基础上&#xff0c;加入了时间维度&#xff0c;形成了四个维度。这种方法通过精准地跟踪和记录动态对象&#xff08;如车辆、行人&#xff09;的运动轨迹、姿势变化以及速度等信息&#xff0c;全面理解和分析动态对…

JWT的登录认证与自校验原理分析

目录 一、JWT的概述 1.什么是JWT&#xff1f; 2.JWT的用户认证 3.JWT解决了什么问题&#xff1f; 4.关于JWT中的签名如何理解&#xff1f; 5.JWT的优势 二、JWT的结构 1.令牌的组成&#xff1a; 2.JWT的工具类 3.JWT所需的依赖 4.JWT登录生成Token的原理 三、JWT的自…