二、链表(linked-list)

news2024/12/24 21:31:59

文章目录

  • 一、定义
  • 二、经典例题
    • (一)[21.合并两个有序链表](https://leetcode.cn/problems/merge-two-sorted-lists/description/)
      • 1.思路
      • 2.复杂度分析
      • 3.注意
      • 4.代码
    • (二)[86.分割链表](https://leetcode.cn/problems/partition-list/description/)
      • 1.思路
      • 2.复杂度分析
      • 3.代码
    • (三)[23.合并 K 个升序链表](https://leetcode.cn/problems/merge-k-sorted-lists/description/)
      • 1.思路
      • 2.复杂度分析
      • 3.代码
    • (四)[19.删除链表中的倒数第N个节点](https://leetcode.cn/problems/remove-nth-node-from-end-of-list/)
      • 1.思路
      • 2.复杂度分析
      • 3.代码

一、定义

链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。 相比于线性表顺序结构,操作复杂。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而线性表和顺序表相应的时间复杂度分别是O(logn)和O(1)。

二、经典例题

(一)21.合并两个有序链表

在这里插入图片描述

1.思路

根据题目描述,链表l1, l2是递增的,因此容易想到使用双指针cur1和cur2遍历两链表,根据cur1.val和cur2.val的大小关系确定节点添加顺序,两节点指针交替前进,直至遍历完毕。
同时因为两个链表都是有序的,所以,当我们遍历完一个链表,剩下的那个链表如果没到结尾,可以直接跟上。

2.复杂度分析

时间复杂度 O(M+N) : M,N别为两个链表的长度,合并操作需遍历两链表。
空间复杂度 O(1): 节点引用 dum , cur使用常数大小的额外空间。

3.注意

Dummy节点的作用是作为一个虚拟的头前节点。在不知道要返回的新链表的头结点是哪一个,它可能是原链表的第一个节点,可能在原链表的中间,也可能在最后,甚至不存在(nil)。引入Dummy节点可以涵盖所有情况,并且可以使用dummy.next返回最终需要的头结点。

4.代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        ListNode* cur1 = list1;
        ListNode* cur2 = list2;
        ListNode* dummy = new ListNode(-1); // 虚拟头结点
        ListNode* cur = dummy;
        while (cur1 && cur2) {
        // 比较 p1 和 p2 两个指针
        // 将值较小的的节点接到 p 指针
            if (cur1 -> val > cur2 -> val) {
                cur -> next = cur2;
                cur2 = cur2 -> next;
            }
            else {
                cur -> next = cur1;
                cur1 = cur1 -> next;
            }
            cur = cur -> next; // p 指针不断前进
        }
        if (cur1) cur -> next = cur1;
        if (cur2) cur -> next = cur2;

        return dummy -> next;
    }
};

(二)86.分割链表

1.思路

具体来说,我们可以把原链表分成两个小链表,一个链表中的元素大小都小于 x,另一个链表中的元素都大于等于 x,最后再把这两条链表接到一起,就得到了题目想要的结果。

2.复杂度分析

时间复杂度 O(N): 其中 N为链表长度;遍历链表使用线性时间。

空间复杂度 O(1) : 假头节点使用常数大小的额外空间。

3.代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* partition(ListNode* head, int x) {
        ListNode* dummy1 = new ListNode(-1);
        ListNode* dummy2 = new ListNode(-1);
        ListNode* small = dummy1;
        ListNode* big = dummy2;
        // 新建两个链表 small,big,分别用于添加所有节点值<x、节点值>=的节点
        ListNode* cur = head;
        while (cur) {
            if (cur -> val >= x) {
                big -> next = cur;
                big = big -> next;
            }
            else {
                small -> next = cur;
                small = small -> next;
            }
            cur = cur -> next;
        }
        small -> next = dummy2 -> next; // 拼接 small 和 big
        big -> next = nullptr;
        return dummy1 -> next;
    }
};

(三)23.合并 K 个升序链表

1.思路

如何快速得到 k 个节点中的最小节点,接到结果链表上?

  • 这里我们就要用到 优先级队列(二叉堆) 这种数据结构,把链表节点放入一个最小堆,就可以每次获得 k 个节点中的最小节点:

优先队列 pq 中的元素个数最多是 k,所以一次 poll 或者 add 方法的时间复杂度是 O(logk);所有的链表节点都会被加入和弹出 pq,所以算法整体的时间复杂度是 O(Nlogk),其中 k 是链表的条数,N 是这些链表的节点总数。

2.复杂度分析

时间复杂度:考虑优先队列中的元素不超过 k 个,那么插入和删除的时间代价为 O(log⁡k),这里最多有 kn 个点,对于每个点都被插入删除各一次,故总的时间代价即渐进时间复杂度为 O(kn×log⁡k)。
空间复杂度:这里用了优先队列,优先队列中的元素不超过 k个,故渐进空间复杂度为 O(k)。

3.代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
// 时间复杂度 : O(n ∗ log(k))
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        if (lists.size() == 0) return nullptr;

        ListNode* dummy = new ListNode(-1);
        ListNode* p = dummy;

       priority_queue<ListNode*, vector<ListNode*>, function<bool(ListNode*, ListNode*)>> pq(
        [] (ListNode* a, ListNode* b) { return a->val > b->val; });

        for (auto head : lists) {
            if (head != nullptr) pq.push(head);
        }

        while (!pq.empty()) {
            ListNode* node = pq.top();
            pq.pop();
            p -> next = node;
            
            if (node -> next != nullptr)
                pq.push(node->next);

            p = p -> next;
        }
    return dummy -> next;
    }
};

(四)19.删除链表中的倒数第N个节点

1.思路

假设链表有 n 个节点,倒数第 k 个节点就是正数第 n - k + 1 个节点。
是的,但是算法题一般只给你一个 ListNode 头结点代表一条单链表,你不能直接得出这条链表的长度 n,而需要先遍历一遍链表算出 n 的值,然后再遍历链表计算第 n - k + 1 个节点。
也就是说,这个解法需要遍历两次链表才能得到出倒数第 k 个节点。

2.复杂度分析

3.代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
         if (head == nullptr || n == 0) return nullptr;

        ListNode* dummy = new ListNode(-1);
        dummy -> next = head;

        ListNode* x = findFromEnd(dummy, n + 1);
        x -> next = x -> next -> next;
        return dummy -> next;
    }
    ListNode* findFromEnd(ListNode* head, int k) {

    // 代码见上文

        ListNode* slow = head;
        ListNode* fast = head;

        while (k -- && fast) {
            fast = fast -> next;
        }
        while (fast) {
            slow = slow -> next;
            fast = fast -> next;
        }
        return slow;
    }
};

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

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

相关文章

批量BMP图片转为PNG透明图片,去掉BMP黑色背景,压缩导出png图片V1.1

前段时间上传了一款bmp转png并去黑底的demo软件&#xff0c;非常受欢迎&#xff0c; 旧版软件介绍文章&#xff1a;批量BMP图片转为PNG透明图片&#xff0c;去掉BMP黑色背景 应广大爱好者的要求&#xff0c;完善了一下软件的功能&#xff0c;增加了导出png图片压缩功能&#x…

本地部署CodeLlama +GTX1080显卡 对接open-interpreter对接wxbot(一)

1.效果展示 开源项目GitHub - oobabooga/text-generation-webui: A Gradio web UI for Large Language Models. Supports transformers, GPTQ, llama.cpp (GGUF), Llama models. "Code Llama" 是一个大型代码语言模型的系列,基于 "Llama 2" 构建,为编程…

【LeetCode-简单题】剑指 Offer 05. 替换空格

文章目录 题目方法一&#xff1a;常规做法&#xff1a;方法二&#xff1a;双指针做法 题目 方法一&#xff1a;常规做法&#xff1a; class Solution {public String replaceSpace(String s) {int len s.length() ;StringBuffer str new StringBuffer();for(int i 0 ; i &l…

JColorChooser 和JFileChooser

语雀笔记&#xff1a;https://www.yuque.com/huangzhanqi/rhwoir/paaoghdyv0tgksk1https://www.yuque.com/huangzhanqi/rhwoir/paaoghdyv0tgksk1Java图形化界面: Java图形化界面学习demo与资料 (gitee.com)https://gitee.com/zhanqi214/java-graphical-interface Swing提供了J…

Linux Day14 :线程的创建与同步

一、简单认知 进程&#xff1a;一个正在运行的程序 线程&#xff1a;进程内部的一个执行路径 头文件&#xff1a;#include<pthread.h> 二、进程与线程的区别 ◼ 进程是资源分配的最小单位&#xff0c;线程是 CPU 调度的最小单位 ◼ 进程有自己的独立地址空间&…

笔记(四)传统图机器学习的特征工程-连接

1、引言 工作&#xff1a;通过已知连接&#xff0c;补全未知连接 思想&#xff1a; 直接提取link特征&#xff0c;把link变成D维向量把link两端节点的D维向量拼接在一起&#xff08;不好&#xff0c;丢失了link本身的连接结构信息&#xff09; 2、连接分类 客观静态图&…

刻字机尖角补偿

1 刻字机尖角补偿原理 刀具切割直线段过渡方法在文章旋转偏心裁切刀切向跟踪及半径补偿 已经有过说明。刻字机由于刀具半径的影响&#xff0c;切割直角时会不直会比较圆滑&#xff0c;而且在闭合曲线的下刀点会容易不闭合。使用尖角补偿可以克服这些问题。 如上图所示&#xf…

大数据Flink(七十七):SQL窗口的Over Windows

文章目录 SQL窗口的Over Windows 一、​​​​​​​时间区间聚合

Say0l的安全开发-代理扫描工具-Sayo-proxyscan【红队工具】

写在前面 终于终于&#xff0c;安全开发也练习一年半了&#xff0c;有时间完善一下项目&#xff0c;写写中间踩过的坑。 安全开发的系列全部都会上传至github&#xff0c;欢迎使用和star。 工具链接地址 https://github.com/SAY0l/Sayo-proxyscan 工具简介 SOCKS4/SOCKS4…

网络安全中的欺骗攻击与防御技术

在Internet上计算机之间相互进行的交流建立在两个前提之下&#xff1a;认证、信任。 认证是网络上的计算机用于相互间进行识别的一种鉴别过程&#xff0c;经过认证的过程&#xff0c;获准相互交流的计算机之间就会建立起相互信任的关系。信任和认证具有逆反关系&#xff0c;即…

振弦传感器信号转换器在岩土工程监测中的注意事项

振弦传感器信号转换器在岩土工程监测中的注意事项 振弦传感器是岩土工程中常用的一种监测设备&#xff0c;用于监测土体和岩体的振动情况。而振弦传感器信号转换器则是将传感器所获得的振动信号转换为人类可读的数据&#xff0c;以方便监测人员进行数据分析和工程决策。 然而&a…

[php] 文件上传的一个项目emmm

项目完整地址 <!DOCTYPE html> <html lang"zh"> <head><meta charset"UTF-8"><title>上传文件</title><link href"./css/bootstrap.min.css" rel"stylesheet"><style>font-face {fo…

亚马逊云科技创新加速周:以数智化手段加速中国企业出海之旅

近年来&#xff0c;越来越多的中国企业正在走向国际市场&#xff0c;中国企业如何在出海浪潮下稳重求进&#xff1f;9月18日-9月22日&#xff0c;新一期亚马逊云科技合作伙伴加速周将为您带来“智荟出海”专题。“智荟出海计划”是亚马逊云科技发布的一项合作计划&#xff0c;旨…

Golang 基础面试题 01

Golang 面试题合集.png 背景 在之前的文章中分享了 k8s 相关的面试题&#xff0c;本文我们重点来讨论和 k8s 密切相关的 Go 语言面试题。 这几年随着云原生的兴起&#xff0c;大部分后端开发者&#xff0c;特别是 Java 开发者都或多或少的想学习一些 Go 相关的技能&#xff0c;…

Postman使用_接口导入导出

文章目录 Postman导入数据Collections导出数据Environments导出数据Postman导出所有数据 Postman导入数据 可以导入collections&#xff08;接口集&#xff09;、Environments&#xff08;环境配置&#xff09;通过分享的链接或导出的JSON文件导入数据&#xff08;还可以从第三…

微信小程序——认识微信小程序

认识小程序 小程序与普通网页开发的区别 运行环境不同 网页运行在浏览器环境中&#xff0c;小程序运行在微信环境中API不同 由于运行环境不同&#xff0c;所以小程序中&#xff0c;无法调用DOM和BOM的API。但是&#xff0c;小程序中可以调用微信环境提供的各种API&#xff0c…

C++之生成详细汇编代码(二百一十六)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

Python绘制柱状图之可视化神器Pyecharts

文章目录 安装Pyecharts绘制基本柱状图自定义柱状图调整柱状图颜色添加数据标签调整柱状图样式添加动画效果堆叠柱状图横向柱状图 更多类型的柱状图堆叠柱状图百分比堆叠柱状图极坐标柱状图 结论 &#x1f389;欢迎来到AIGC人工智能专栏~Python绘制柱状图之可视化神器Pyecharts…

让AI为您自动生成独特的商品标题,商品描述和营销文案

做外贸的朋友你知道吗&#xff1f;AI可为您自动生成独特的商品标题&#xff0c;商品描述和营销文案 AI生成的商品标题是一项强大的工具&#xff0c;尤其在外贸和跨境电商行业中&#xff0c;它可以帮助您轻松创造引人注目的标题&#xff0c;吸引潜在客户。以下是一个针对外贸和…

JDK 动态代理

动态代理机制 Java 里面&#xff0c;动态代理是通过 Proxy.newProxyInstance()方法来实现的&#xff0c;它需要传入被动态代理的接口类 // 生成代理文件写入磁盘 System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");ITrad…