【算法刷题之链表篇(2)】

news2024/12/23 13:23:53

目录

  • 1.leetcode-23. 合并 K 个升序链表(较难)
    • (1)题目描述
    • (2)方法一:顺序合并
    • (3)方法二:分治合并
    • (4)方法三:使用优先队列合并
  • 2.leetcode-92. 反转链表 II
    • (1)题目描述
    • (2)方法及思路(穿针引线)
    • (3)代码实现
  • 3.leetcode-2. 两数相加
    • (1)题目描述
    • (2)方法及思路(模拟)
    • (3)代码实现
  • 4.leetcode-21. 合并两个有序链表
    • (1)题目描述
    • (2)方法一:迭代
    • (3)方法二:递归
  • 5.leetcode-24. 两两交换链表中的节点
    • (1)题目描述
    • (2)方法一:迭代
    • (3)方法二:递归

1.leetcode-23. 合并 K 个升序链表(较难)

(1)题目描述

给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
在这里插入图片描述

(2)方法一:顺序合并

思路
首先写出合并两个链表的代码
1.定义两个指针分别位于两个链表的头处,再定义一个哨兵位用于接受元素;
2.两个链表头处的指针开始遍历,并且相互比较,此时哨兵位newnode的节点head指向比较中更小的那个元素。
3.将此链表往前移一位,并且哨兵位的head也往前移动以为。
重复上述步骤即可合并两个链表;
对于k组链表的合并
对于所给k组链表,从头开始遍历,逐步将两个链表先合并为一个,然后再与后面合并,这样就可以调用上述的两两合并的函数
代码实现

class Solution {
public:
    ListNode* mergeTwoLists(ListNode *a, ListNode *b) {
        if ((!a) || (!b)) return a ? a : b;
        ListNode head, *tail = &head, *aPtr = a, *bPtr = b;
        while (aPtr && bPtr) {
            if (aPtr->val < bPtr->val) {
                tail->next = aPtr; aPtr = aPtr->next;
            } else {
                tail->next = bPtr; bPtr = bPtr->next;
            }
            tail = tail->next;
        }
        tail->next = (aPtr ? aPtr : bPtr);
        return head.next;
    }

    ListNode* mergeKLists(vector<ListNode*>& lists) {
        ListNode *ans = nullptr;
        for (size_t i = 0; i < lists.size(); ++i) {
            ans = mergeTwoLists(ans, lists[i]);
        }
        return ans;
    }
};

(3)方法二:分治合并

考虑优化方法一,用分治的方法进行合并。
将 k 个链表配对并将同一对中的链表合并;
第一轮合并以后, k 个链表被合并成了k/2个链表,然后是k/4
重复这一过程,直到我们得到了最终的有序链表。
在这里插入图片描述

class Solution {
public:
    ListNode* mergeTwoLists(ListNode *a, ListNode *b) {
        if ((!a) || (!b)) return a ? a : b;
        ListNode head, *tail = &head, *aPtr = a, *bPtr = b;
        while (aPtr && bPtr) {
            if (aPtr->val < bPtr->val) {
                tail->next = aPtr; aPtr = aPtr->next;
            } else {
                tail->next = bPtr; bPtr = bPtr->next;
            }
            tail = tail->next;
        }
        tail->next = (aPtr ? aPtr : bPtr);
        return head.next;
    }

    ListNode* merge(vector <ListNode*> &lists, int l, int r) {
        if (l == r) return lists[l];
        if (l > r) return nullptr;
        int mid = (l + r) >> 1;
        return mergeTwoLists(merge(lists, l, mid), merge(lists, mid + 1, r));
    }

    ListNode* mergeKLists(vector<ListNode*>& lists) {
        return merge(lists, 0, lists.size() - 1);
    }
};

(4)方法三:使用优先队列合并

思路
这个方法和前两种方法的思路有所不同,我们需要维护当前每个链表没有被合并的元素的最前面一个,k 个链表就最多有 k 个满足这样条件的元素,每次在这些元素里面选取 val 属性最小的元素合并到答案中。在选取最小元素的时候,我们可以用优先队列来优化这个过程。
代码实现

class Solution {
public:
    struct Status {
        int val;
        ListNode *ptr;
        bool operator < (const Status &rhs) const {
            return val > rhs.val;
        }
    };
    priority_queue <Status> q;
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        for (auto node: lists) {
            if (node) q.push({node->val, node});
        }
        ListNode head, *tail = &head;
        while (!q.empty()) {
            auto f = q.top(); q.pop();
            tail->next = f.ptr; 
            tail = tail->next;
            if (f.ptr->next) q.push({f.ptr->next->val, f.ptr->next});
        }
        return head.next;
    }
};

代码解释:
这段代码是一个用于合并K个有序链表的解决方案。它使用了优先队列(priority_queue)来选择当前K个链表中最小的节点,并将其连接到结果链表中。
在mergeKLists函数中,定义了一个结构体Status,用于存储节点的值和指针。结构体中还重载了小于运算符,以便在优先队列中按照节点值的大小进行排序。
首先,将K个链表中的第一个节点加入到优先队列中。然后,创建一个虚拟头节点head和一个指针tail指向它。接下来,进入循环,直到优先队列为空。在每次循环中,取出优先队列中的最小节点f,将其连接到结果链表的末尾,即tail->next = f.ptr,然后更新tail指针为新的末尾节点。如果最小节点f的指针f.ptr还有下一个节点,将下一个节点的值和指针加入到优先队列中。重复这个过程,直到所有链表中的节点都被处理完。
最后,返回结果链表的头节点,即head.next。

2.leetcode-92. 反转链表 II

(1)题目描述

给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。
(注意left和right是位置)
在这里插入图片描述

(2)方法及思路(穿针引线)

思路示意图如下
在这里插入图片描述
如图所示,我们需要找到left前一个结点,也需要找到right后一个节点,
并在反转链表前将其与前后断开。
具体思路如下
.第 1 步:先将待反转的区域反转;
第 2 步:把 pre 的 next 指针指向反转以后的链表头节点,把反转以后的链表的尾节点的 next 指针指向 succ。
在这里插入图片描述

(3)代码实现

class Solution {
    private:
        void reverseLinkedList(ListNode* head)
        {
            ListNode* pre=NULL;
            ListNode* cur=head;
            while(cur!=NULL)
            {
                ListNode* next=cur->next;
                cur->next=pre;
                pre=cur;
                cur=next;
            }
        }
   public:
    ListNode* reverseBetween(ListNode* head, int left, int right) {
        ListNode* newnode=new ListNode(0);
        ListNode* prev=newnode;
        prev->next=head;
       for (int i = 0; i < left - 1; i++) {
            prev = prev->next;
        }
        ListNode* leftnode=prev->next;
        ListNode* rightnode=prev;
        while(right-left+1>0)
        {
            rightnode=rightnode->next;
            right--;
        }
        ListNode* curr=rightnode->next;
        prev->next=NULL;
        rightnode->next=NULL;
        reverseLinkedList(leftnode);
        prev->next=rightnode;
        leftnode->next=curr;
        return newnode->next;
    }
};

3.leetcode-2. 两数相加

(1)题目描述

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
在这里插入图片描述

(2)方法及思路(模拟)

思路:由于输入的两个链表都是逆序存储数字的位数的,因此两个链表中同一位置的数字可以直接相加。
我们同时遍历两个链表,逐位计算它们的和,并与当前位置的进位值相加。具体而言,如果当前两个链表处相应位置的数字为 n1,n2,进位值为 carry,则它们的和为 n1+n2+carry,其中,答案链表处相应位置的数字为(n1+n2+carry)%10,而新的进位值为(n1+n2+carry)/10。

(3)代码实现

class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        ListNode *head = nullptr, *tail = nullptr;
        int carry = 0;
        while (l1 || l2) {
            int n1 = l1 ? l1->val: 0;
            int n2 = l2 ? l2->val: 0;
            int sum = n1 + n2 + carry;
            if (!head) {
                head = tail = new ListNode(sum % 10);
            } else {
                tail->next = new ListNode(sum % 10);
                tail = tail->next;
            }
            carry = sum / 10;
            if (l1) {
                l1 = l1->next;
            }
            if (l2) {
                l2 = l2->next;
            }
        }
        if (carry > 0) {
            tail->next = new ListNode(carry);
        }
        return head;
    }
};

4.leetcode-21. 合并两个有序链表

(1)题目描述

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
在这里插入图片描述

(2)方法一:迭代

思路
我们可以用迭代的方法来实现上述算法。当 l1 和 l2 都不是空链表时,判断 l1 和 l2 哪一个链表的头节点的值更小,将较小值的节点添加到结果里,当一个节点被添加到结果里之后,将对应链表中的节点向后移一位。
算法
首先,我们设定一个哨兵节点 prehead ,这可以在最后让我们比较容易地返回合并后的链表。我们维护一个 prev 指针,我们需要做的是调整它的 next 指针。然后,我们重复以下过程,直到 l1 或者 l2 指向了 null :如果 l1 当前节点的值小于等于 l2 ,我们就把 l1 当前的节点接在 prev 节点的后面同时将 l1 指针往后移一位。否则,我们对 l2 做同样的操作。不管我们将哪一个元素接在了后面,我们都需要把 prev 向后移一位。
在循环终止的时候, l1 和 l2 至多有一个是非空的。由于输入的两个链表都是有序的,所以不管哪个链表是非空的,它包含的所有元素都比前面已经合并链表中的所有元素都要大。这意味着我们只需要简单地将非空链表接在合并链表的后面,并返回合并链表即可。
代码实现

class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode prehead = new ListNode(-1);

        ListNode prev = prehead;
        while (l1 != null && l2 != null) {
            if (l1.val <= l2.val) {
                prev.next = l1;
                l1 = l1.next;
            } else {
                prev.next = l2;
                l2 = l2.next;
            }
            prev = prev.next;
        }
        prev.next = l1 == null ? l2 : l1;
        return prehead.next;
    }
}

(3)方法二:递归

思路
我们可以如下递归地定义两个链表里的 merge 操作(忽略边界情况,比如空链表等):
在这里插入图片描述
1.如果 l1 或者 l2 一开始就是空链表 ,那么没有任何操作需要合并,所以我们只需要返回非空链表。
2.否则,我们要判断 l1 和 l2 哪一个链表的头节点的值更小,然后递归地决定下一个添加到结果里的节点。
3.如果两个链表有一个为空,递归结束。
代码如下:

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        if(list1==NULL)
        {
            return list2;
        }
        else if(list2==NULL)
        {
            return list1;
        }
        else if(list1->val<list2->val)
        {
            list1->next=mergeTwoLists(list1->next,list2);
            return list1;
        }
        else
        {
            list2->next=mergeTwoLists(list1,list2->next);
            return list2;
        }
    }
};

5.leetcode-24. 两两交换链表中的节点

(1)题目描述

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
在这里插入图片描述

(2)方法一:迭代

思路:
1.首先创建一个哨兵位newnode,并定义一个指针prev指向它。
2.在prev后定义一个当前节点n1,后一个为n2,并先将prev指向n2,n2,指向n1,再把n1指向原来n2的下一位
3.最后更新prev节点到n1的新位置。

代码实现

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        ListNode* newnode=new ListNode(0);
        ListNode* prev=newnode;
        prev->next=head;
        while(prev->next&&prev->next->next)
        {
            ListNode* n1=prev->next;
            ListNode* n2=prev->next->next;
            prev->next=n2;
            n1->next=n2->next;
            n2->next=n1;
            prev=n1;
        }
        return newnode->next;
    }
};

(3)方法二:递归

思路与算法
可以通过递归的方式实现两两交换链表中的节点。
递归的终止条件是链表中没有节点,或者链表中只有一个节点,此时无法进行交换。
1.如果链表中至少有两个节点,则在两两交换链表中的节点之后,原始链表的头节点变成新的链表的第二个节点,原始链表的第二个节点变成新的链表的头节点。
2.链表中的其余节点的两两交换可以递归地实现。在对链表中的其余节点递归地两两交换之后,更新节点之间的指针关系,即可完成整个链表的两两交换。

代码实现:

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;
    }
};

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

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

相关文章

2023国赛数学建模E题思路模型代码 高教社杯

本次比赛我们将会全程更新思路模型及代码&#xff0c;大家查看文末名片获取 之前国赛相关的资料和助攻可以查看 2022数学建模国赛C题思路分析_2022国赛c题matlab_UST数模社_的博客-CSDN博客 2022国赛数学建模A题B题C题D题资料思路汇总 高教社杯_2022国赛c题matlab_UST数模社…

C# 设置、获取程序,产品版本号

右键&#xff0c;程序属性。打开“程序集信息” 选择需要设置的版本信息。下面的代码&#xff0c;获取不同的设置内容。 string 其他 Assembly.GetExecutingAssembly().FullName; string 程序集版本 Assembly.GetExecutingAssembly().G…

(杭电多校)2023“钉耙编程”中国大学生算法设计超级联赛(10)

1003 Many Topological Problems 每个节点序号和权值分开计算,两者的排列组合数相乘即为答案 对于序号的顺序,一共有n个位置,第一个位置可以放序号1,2,..n共n个点,第二个则可放置n-1个点,以此类推,排列组合数为n的阶乘 对于权值,从小到大放置,如果不考虑k的话,对于权值为x的数,…

企培版edusoho对接第三方云视频点播 最新版本代码披露 支持m3u8视频加密

edusoho企培系列版本更新日志&#xff1a;新增功能和优化历史 倍数播放功能、视频分类、支持m3u8视频加密 \plugins\AliVideoPlugin\DependencyInjection\Configuration.php <?php namespace AliVideoPlugin\DependencyInjection; use Symfony\Component\Config\Definiti…

7-9 说反话-加强版

分数 20 全屏浏览题目 切换布局 作者 陈越 单位 浙江大学 给定一句英语&#xff0c;要求你编写程序&#xff0c;将句中所有单词的顺序颠倒输出。 输入格式&#xff1a; 测试输入包含一个测试用例&#xff0c;在一行内给出总长度不超过500 000的字符串。字符串由若干单词和…

Docker创建 LNMP 服务+Wordpress 网站平台

Docker创建 LNMP 服务Wordpress 网站平台 一.环境及准备工作 1.项目环境 公司在实际的生产环境中&#xff0c;需要使用 Docker 技术在一台主机上创建 LNMP 服务并运行 Wordpress 网站平台。然后对此服务进行相关的性能调优和管理工作。 容器 系统 IP地址 软件 nginx centos…

Log4j反序列化命令执行漏洞(CVE-2017-5645)Apache Log4j2 lookup JNDI 注入漏洞(CVE-2021-44228)

一.Log4j反序列化命令执行漏洞(CVE-2017-5645&#xff09; Apache Log4j是一个用于Java的日志记录库&#xff0c;其支持启动远程日志服务器。Apache Log4j 2.8.2之前的2.x版本中存在安全漏洞。攻击者可利用该漏洞执行任意代码 环境&#xff1a;vulhub 工具下载地址&#xff1…

Docker容器:docker基础及网络

Docker容器&#xff1a;docker基础及安装 一.docker容器概述 1.什么是容器 &#xff08;1&#xff09;Docker是在Linux容器里运行应用的开源工具&#xff0c;是一种轻量级的“虚拟机”。 &#xff08;2&#xff09;是一个开源的应用容器引擎&#xff0c;基于go语言开发并遵…

sh 脚本循环语句和正则表达式

目录 1、循环语句 1、for 2、while 3、until 2、正则表达式 1、元字符 2、表示次数 3、位置锚定 4、分组 5、扩展正则表达式 1、循环语句 循环含义 将某代码段重复运行多次&#xff0c;通常有进入循环的条件和退出循环的条件 重复运行次数 循环次数事先已知 循环次…

RS232、RS422、RS485硬件及RS指令、RS2指令应用知识学习

RS232、RS422、RS485硬件及RS指令、RS2指令应用知识学习 一、串行&#xff08;异步/同步)通讯、并行通讯、以太网通讯 二、单工通讯/半双工通讯/双工通讯 三、常用硬件接口&#xff08;工业上基本是RS485两线制的接线&#xff09; 常用硬件接口RS232/RS422/RS485&#xff0c;…

Delegates.observable追踪观察可变数据更新,Kotlin

Delegates.observable追踪观察可变数据更新&#xff0c;Kotlin import kotlin.properties.Delegates import kotlin.reflect.KPropertyclass Person {var name: String by Delegates.observable("fly") { prop: KProperty<*>, old: String, new: String ->p…

SpringBoot 学习(04):Idea 中控制启动命令的详细过程 环境区分案例

Idea 启动SpringBoot的命令 C:\Users\Administrator\.jdks\corretto-17.0.8\bin\java.exe -XX:TieredStopAtLevel1 -Dspring.output.ansi.enabledalways -Dcom.sun.management.jmxremote -Dspring.jmx.enabledtrue -Dspring.liveBeansView.mbeanDomain -Dspring.applica…

EasyRecovery14数据恢复软件支持各类存储设备的数据恢复

EasyRecovery14数据恢复软件专业数据恢复软件支持电脑、相机、移动硬盘、U盘、SD卡、内存卡、光盘、本地电子邮件和 RAID 磁盘阵列等各类存储设备的数据恢复。 目前市面上有许多数据恢复软件&#xff0c;但褒贬不一&#xff0c;而且数据恢复软件又不是一款会被经常使用的软件&a…

【多模态】26、视觉-文本多模态任务超详细介绍 「CLIP/LSeg/ViLD/GLIP/ALBEF/BLIP/CoCa/BEIT」

文章目录 准备知识一、CLIP&#xff1a;不同模态简单对比的方法更适合于图文检索1.1 CLIP 在分割上的改进工作1.1.1 LSeg1.1.2 Group ViT 1.2 CLIP 在目标检测上的改进工作1.2.1 ViLD1.2.2 GLIPv11.2.3 GLIPv2 二、ViLT/ALBEF &#xff1a;多模态融合在 VQA/VR 任务中更重要三、…

基于protobuf和httplib的在线通讯录项目框架|Protobuf应用小项目

前言 那么这里博主先安利一些干货满满的专栏了&#xff01; 首先是博主的高质量博客的汇总&#xff0c;这个专栏里面的博客&#xff0c;都是博主最最用心写的一部分&#xff0c;干货满满&#xff0c;希望对大家有帮助。 高质量博客汇总https://blog.csdn.net/yu_cblog/categ…

学习设计模式之适配器模式,但是宝可梦

前言 作者在准备秋招中&#xff0c;学习设计模式&#xff0c;做点小笔记&#xff0c;用宝可梦为场景举例&#xff0c;有错误欢迎指出。 适配器模式 意图&#xff1a;将一个类的接口转换成客户希望的另一个接口 主要解决&#xff1a;把现有对象放到新环境里&#xff0c;而新…

leetcode几个数组题

数组理论基础 数组是存放在连续内存空间上的相同类型数据的集合 因为数组的在内存空间的地址是连续的&#xff0c;所以我们在删除或者增添元素的时候&#xff0c;就难免要移动其他元素的地址 二分查找 移除元素 有序数组的平方 209.长度最小的子数组

设计模式——开闭原则

文章目录 基本介绍看下面一段代码方式 1 的优缺点改进的思路分析 基本介绍 开闭原则&#xff08;Open Closed Principle&#xff09;是编程中最基础、最重要的设计原则 一个软件实体如类&#xff0c;模块和函数应该对扩展开放(对提供方)&#xff0c;对修改关闭(对使用方)。用抽…

wustoj2008折扣

#include <stdio.h> int main() {int n,m;double c;scanf("%d%d",&n,&m);cn*(m/10.00);printf("%.2lf",c);return 0;}

茂名 湛江阳江某学校 ibm x3850服务器维修经历

简介&#xff1a;中国广东省阳江市某中学联想 IBM System x3850 x6服务器维修 io板故障处理经历分享&#xff1a; 这一天一位阳江的老师经其他学校老师介绍推荐对接我&#xff0c;说他们学校有一台ibm服务器出问题了&#xff0c;老师大致跟我描述了一下这台服务器发生故障的前…