算法——链表

链表常用技巧

  1. 画图分析!!!!!!!!!!——直观形象,便于理解、大多数都是模拟
  2. 引入虚拟头结点(哨兵位)
    1. 典型的就是在第一个节点传空指针,此时我们如果解引用,程序直接崩掉

image.png

  1. 我们一般选择创建哨兵位头结点,这里我们哨兵位不存储数据。解释传入空链表,这里的newhead只需要特判一下即可image.png

  2. 不要吝啬空间,大胆定义变量。

    1. 我们经常会遇到这种题目,将cur插入到prev后面。途中四句是正确的顺序,在考试题目中经常会遇到将这四句顺序打乱的情况。其实我们只需要保证前两句先执行即可(五角星标志的),这样是为了防止prev找不到next。如果先执行后两句,那么prev链接cur之后,就找不到next节点

image.png

  2. 我们也可以定义next指针,这样就不需要考虑下面四句语句的执行顺序

image.png

  1. 链表快慢双指针,典型题目有:判环;找链表中环的入口;找链表中倒数第n个节点

链表中常用操作

  1. 创建一个新节点 new
  2. 尾插:先定义一个指针tail指向最后一个位置,让tail->next 指向新节点,然后将尾结点转移到新节点上。image.png
  3. 头插:让新节点的next指向newhead->next,此时如果之前给的链表为空,让新节点next指向空,不会发生访问空节点的报错。然后让newhead->next指向新节点。如果怕链表断开,可以新定义一个next指针。我们也可以利用这个方法完成逆序链表

image.pngimage.png

两数相加

两数相加

题目解析

image.png
这里给我们逆序存储链表反而有利于我们做题,我们相加时是从最低位开始加,如果题里给我们正常顺序存储的链表的话,我们还需要自己逆置。

算法原理

解法:模拟两数相加的过程

  • 这里创建一个newhead用于链接最终结果。如果这里我们不用newhead链接,我们需要先把两个链表头结点相加然后存储新的head用来记录结果(因为最后要返回链表),然后再继续往后相加。这样我们操作就多了一步。

image.png

  • 用一个变量t来标记我们的进位,然后定义两个指针模拟加法。只要cur1、cur2不为空,就把他们指向的数字移到t中,然后把t中个位数字提出来(因为数字相加可能变为两位数,牵扯到进位),然后把new一个节点7,链接到到我们的newhead后面。同理…

image.pngimage.png

代码实现

/**
 * 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* addTwoNumbers(ListNode* l1, ListNode* l2) 
    {
    ListNode* cur1 = l1, *cur2 = l2;
    ListNode* newhead = new ListNode(0); // 创建⼀个虚拟头结点,记录最终结果
    ListNode* prev = newhead; // 尾指针
    int t = 0; // 记录进位
while(cur1 || cur2 || t)
{
 // 先加上第⼀个链表
    if(cur1)
    {
    t += cur1->val;
    cur1 = cur1->next;
    }

// 加上第⼆个链表
    if(cur2)
    {
    t += cur2->val;
    cur2 = cur2->next;
    }
  prev->next = new ListNode(t % 10);
  prev = prev->next;
  t /= 10;
}
    prev = newhead->next;
    delete newhead;
    return prev;
    }
};

两两交换链表中的节点

两两交换链表中的节点

题目解析

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

image.png

算法原理

解法:循环迭代:

  • 这里同样引入一个虚拟节点newhead链接链表。如果不引入头结点,在我们交换前两个节点的时候,因为前面没有节点。所以我们要先定义一个变量找到最终要返回的头结点。因为前半部分1、2两个节点是不需要将修改将前面的指针指向2,但是后面部分3、4需要交换过后,让前面位置的指针指向4链接。这时需要写两段代码分别写两种情况。image.png
  • 不要吝啬我们的空间,定义四个指针,紫色情况是交换1、2,绿色情况是交换3、4image.png
  • 当3、4交换完成时,四个指针移动如下情况:
    • 当链表是奇数个节点,这时cur有可能访问空指针,所以我们需要终止循环image.png
    • 当链表是偶数个节点时,next指向空,需要终止循环。image.png

所以终止循环条件:cur或者next有一个指向空的时候,就终止循环

代码实现

/**
 * 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* swapPairs(ListNode* head) {
    if(head == nullptr || head->next == nullptr) return head;
    ListNode* newHead = new ListNode(0);
    newHead->next = head;
    ListNode* prev = newHead, *cur = prev->next, *next = cur->next, *nnext =next->next;
    while(cur && next)
    {
// 交换结点
    prev->next = next;
    next->next = cur;
    cur->next = nnext;
// 修改指针
    prev = cur; // 注意顺序
    cur = nnext;
    if(cur) next = cur->next;
    if(next) nnext = next->next;
    }
    cur = newHead->next;
    delete newHead;
    return cur;
    }
};

重排链表

重排链表

题目解析

  • 第一个、倒数第一个、第二个、倒数第二个image.png

算法原理

**解法:模拟——**其实很像两个链表合并,一个是正向的1、2,一个是倒向的链表4、3。恰好是原始链表的前半部分,和后半部分逆序。

所以我们的策略是:先找到链表的中点,然后把后面部分的链表逆序;然后把前面的部分和后面的部分一个一个交互链接,合并一起即可。image.png

  1. 找到链表的中间节点(⼀定要画图考虑 slow 的落点在哪⾥ )

    1. 利用快慢双指针。这里注意一下节点个数是奇数和偶数的情况。slow=slow->next;fast=fast->next->next;我们的停止循环的条件是当fast或fast->next为空。如下图分别是节点个数为奇数和偶数的情况。此时观察slow落在中点的情况:
      1. 我们可以让slow->next部分直接翻转。这里我们可能会有疑惑,难道不应该让slow后面的部分整体翻转吗?这是这道题的特殊性,我们还是用1234进行举例子,我们发现重排前和重拍后发现2和3的链接部分是不变的,所以我们可以把slow指向的这个节点大胆给第一个链表。重排后发现是不影响结果的。

image.png

  2. 我们也可以让slow所指的开始翻转(红色笔标注的)。此时无非是把slow所指的节点丢给后半部分,逆序合并后发现结果并不影响![image.png](https://img-blog.csdnimg.cn/img_convert/77049b1c441cb9b3336de5f341cee1c9.png)
  1. 用头插法,如果这里使用头插法,建议使用第一个方法(slow->next翻转)。因为最终是断开成两个链表,
    1. 如果从slow所指的位置开始逆置,会发现箭头所指的位置指针无法断开。有可能出现意想不到的错误;

image.png

  2. 当我们选择slow->next位置开始翻转,我们只需让slow所指的位置置空即可,

image.png

  3. 如果我们只能用slow位置开始翻转的情况时,我们可以引入一个虚拟头结点newhead(哨兵位),去链接head,然后在快慢指针找中间节点,之前是slow位置,就会变成slow->next,这时候也能将两个链表断开。
  1. 把slow后面部分逆序——头插法(使用虚拟头结点)
    • 把slow后面的节点,头插到新建的头(哨兵位)结点后面。当让cur头插到newhead后面时,先让cur->next指向空(就是newhead2后面的节点),然后newhead2->next指向cur,此时newhead2就与null(原先后面的节点)断开
    • 当newhead2后面有节点时,按照上面的操作我们会发现,newhead2会与后面的断开了,所以在刚开始头插时我们要定义一个next指针标记newhead2后面的节点。当执行完上面操作后,让cur=nextimage.png
  2. 合并两个链表
    1. 我们是创建一个虚拟节点,让他们都尾插在这后面
    2. cur1相较于cur2会长一些,所以循环判空条件为cur1为空。

代码实现

/**
 * 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:
    void reorderList(ListNode* head) 
    {
    // 处理边界情况
    if(head == nullptr || head->next == nullptr || head->next->next == nullptr)
    {
    	return ;
    }
    
 // 1. 找到链表的中间节点 - 快慢双指针(⼀定要画图考虑 slow 的落点在哪⾥)
    ListNode* slow = head, *fast = head;
   
    while(fast && fast->next)
    {
     slow = slow->next;
     fast = fast->next->next;
    }

// 2. 把 slow 后⾯的部分给逆序 - 头插法
    ListNode* head2 = new ListNode(0);
    ListNode* cur = slow->next;
    slow->next = nullptr; // 注意把两个链表给断开   
    while(cur)
    {
        ListNode* next = cur->next;
        cur->next = head2->next;
        head2->next = cur;
        cur = next;
    }

// 3. 合并两个链表 - 双指针
    ListNode* ret = new ListNode(0);
    ListNode* prev = ret;
    ListNode* cur1 = head, *cur2 = head2->next;
    while(cur1)
    {
// 先放第⼀个链表
    prev->next = cur1;
    cur1 = cur1->next;
    prev = prev->next;

// 再放第⼆个链表
        if(cur2)
        {
        prev->next = cur2;
        prev = prev->next;
        cur2 = cur2->next;
        }
    }
delete head2;
delete ret;
}
};

合并k个升序链表

合并k个升序链表

题目解析

image.png

算法原理

**解法一:暴力解法:**逐个合并.其中n是链表的长度,k为链表的个数image.png

解法二:优先级队列做优化
我们可以直接定义k个指针,仿照合并k个有序链表,把这些链表中较小的头结点放到newhead后面,放完之后右移,继续比较各个节点最小的,把最小的连接在newhead后面即可。这样只需要遍历一遍链表即可。O(n*k)是理想状态下的时间复杂度,我们还要找出k个节点中最小的节点。这里我们可以利用优先级队列

把k个节点丢入小根堆里,经过排序后,堆顶元素即为最小的元素,链接到newhead后面后,将移动后下一个节点放入小根堆里。这里时间复杂度O(nklogk)
image.png

解法三:分治——递归
流程:

  1. 特判,如果题⽬给出空链表,⽆需合并,直接返回;
  2. 返回递归结果。


递归函数设计:

  1. 递归出⼝:如果当前要合并的链表编号范围左右值相等,⽆需合并,直接返回当前链表;
  2. 应⽤⼆分思想,等额划分左右两段需要合并的链表,使这两段合并后的⻓度尽可能相等;
  3. 对左右两段分别递归,合并[l,r]范围内的链表
  4. 再调⽤mergeTwoLists函数进⾏合并(就是合并两个有序链表)

时间复杂度:一共k个链表,每个链表里面有n个节点,每一层我们都平均分,即高度为logk,每一个节点都执行了logk次合并(这棵树的高度次)image.png

代码实现

class Solution
{
struct cmp
 {
    bool operator()(const ListNode* l1, const ListNode* l2)
    {
       return l1->val > l2->val;
    }
 };

public:
ListNode* mergeKLists(vector<ListNode*>& lists)
{
    // 创建⼀个⼩根堆
 priority_queue<ListNode*, vector<ListNode*>, cmp> heap;

// 让所有的头结点进⼊⼩根堆
    for(auto l : lists)
        if(l) heap.push(l);

// 合并 k 个有序链表
ListNode* ret = new ListNode(0);
ListNode* prev = ret;
while(!heap.empty())
 {
    ListNode* t = heap.top();
    heap.pop();
    prev->next = t;
    prev = t;
    if(t->next) heap.push(t->next);
 }

prev = ret->next;
delete ret;
return prev;
}
};
/**
 * 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* mergeKLists(vector<ListNode*>& lists)
    {
        return merge(lists, 0, lists.size() - 1);
    }

    ListNode* merge(vector<ListNode*>& lists, int left, int right)
    {
        if(left > right) return nullptr;
        if(left == right) return lists[left];

    // 1. 平分数组
    int mid = left + right >> 1;

    // [left, mid] [mid + 1, right]
    // 2. 递归处理左右区间
    ListNode* l1 = merge(lists, left, mid);
    ListNode* l2 = merge(lists, mid + 1, right);

    // 3. 合并两个有序链表
    return mergeTowList(l1, l2);
    }

    ListNode* mergeTowList(ListNode* l1, ListNode* l2)
    {
        if(l1 == nullptr) return l2;
        if(l2 == nullptr) return l1;
        
        // 合并两个有序链表
        ListNode head;
        ListNode* cur1 = l1, *cur2 = l2, *prev = &head;
        head.next = nullptr;
    while(cur1 && cur2)
    {
        if(cur1->val <= cur2->val)
        {
            prev = prev->next = cur1;
            cur1 = cur1->next;
        }

        else
        {
            prev = prev->next = cur2;
            cur2 = cur2->next;
        }
    }

    if(cur1) prev->next = cur1;
    if(cur2) prev->next = cur2;
    return head.next;
    }
};

k个一组翻转链表

k个一组翻转链表

题目解析

链表中的节点每k个一组进行翻转,如果剩余的链表不够k个,则不翻转image.png

算法原理

解法:模拟

  1. 先求出需要逆序多少组n:遍历链表一遍 n=链表节点个数/k
  2. 重复n次长度为k的链表的逆序
    1. 利用头插法逆序,创建一个newhead。头插时,cur->next=head->next;head->next = cur;此时右边节点(2)会丢失,需要先用一个next指针记录一下节点,然后头插。头插完之后cur++,直至头插3之后,这一组完成。

image.png

  1. 进行下一组头插时,需要将4头插到1的后面,所以需要一个指针标记一下1的位置,这个位置是每一组开始头插的第一个位置。所以每组第一个位置开始头插的时候,要标记好开始头插的位置(tmp),还需要定义个prev标记将要头插位置的前驱,每当一组头插完,该进行下一组时,让prev更新到tmp位置。image.pngimage.png

代码实现

/**
 * 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* reverseKGroup(ListNode* head, int k) {
    // 1. 先求出需要逆序多少组
    int n = 0;
    ListNode* cur = head;
    while(cur)
    {
        cur = cur->next;
        n++;
    }
    n /= k;

 // 2. 重复 n 次:⻓度为 k 的链表的逆序即可
    ListNode* newHead = new ListNode(0);
    ListNode* prev = newHead;
    cur = head;
    for(int i = 0; i < n; i++)
    {
        ListNode* tmp = cur;
        for(int j = 0; j < k; j++)
        {
        ListNode* next = cur->next;
        cur->next = prev->next;
        prev->next = cur;
        cur = next;
        }

        prev = tmp;
    }

// 把不需要翻转的接上
	prev->next = cur;
	cur = newHead->next;
	delete newHead;
	return cur;
	}
};

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Redis设计与实现之服务器与客户端

目录 一、服务器与客户端 1、初始化服务器 1. 初始化服务器全局状态 2. 载入配置文件 3. 创建 daemon 进程 4. 初始化服务器功能模块 5. 载入数据 6. 开始事件循环 2、 客户端连接到服务器 3、命令的请求、处理和结果返回 4、命令请求实例:SET 的执行过程 5、Redis服…

【浏览器】同源策略和跨域

1. 什么是跨域 在说跨域之前,先说说同源策略,什么是同源策略呢?同源策略是浏览器的一种安全机制,减少跨站点脚本攻击(XSS,Cross Site Scripting)、跨站点请求伪造(CSRF,Cross Site Request Forgery)攻击等,因为非同源的请求会被浏览器拦截掉。 同源就是协议、域名(…

电路设计(7)——窗口比较器的multism仿真

1.功能设计 构建一个窗口比较器的电路&#xff0c;在输入电压大于3.5v&#xff0c;小于0.8v时&#xff0c;蜂鸣器报警&#xff0c;输入电压在0.8v到3.5v之间时&#xff0c;不报警。 整体电路如下&#xff1a; 2.设计思路 在输入端&#xff0c;采取电阻分压的方式&#xff0c;输…

ChatGPT/GPT4+AI绘图+论文写作+编程结合到底有多强大?带你详细了解

ChatGPT在论文写作与编程方面具备强大的能力。无论是进行代码生成、错误调试还是解决编程难题&#xff0c;ChatGPT都能为您提供实用且高质量的建议和指导&#xff0c;提高编程效率和准确性。此外&#xff0c;ChatGPT是一位出色的合作伙伴&#xff0c;可以为您提供论文写作的支持…

Jenkins自动化部署之后端

准备工作参考本人另外几篇Jenkins相关的文章 新建任务 添加参数配置 字符串参数&#xff1a;分支名称 多选框&#xff1a;项目名称&#xff08;Extended Choice Parameter插件必备&#xff0c;插件安装参考我另外的文章&#xff09; 这个分割规则自定义。只要根据Jenkins…

【Java】MybatisPlus

MybatisPlus MybatisPlus是在mybatis基础上的一个增强型工具。它对mybatis的一些操作进行了简化&#xff0c;能够提高开发的效率。 springboot整合了mybatis之后&#xff0c;其实已经非常方便了&#xff0c;只需要导入mybatis的包后&#xff0c;在配置文件中编写数据源信息&a…

MySQL的替换函数及补全函数的使用

前提&#xff1a; mysql的版本是8.0以下的。不支持树形结构递归查询的。但是&#xff0c;又想实现树形结构的一种思路 提示&#xff1a;如果使用的是MySQL8.0及其以上的&#xff0c;想要实现树形结构&#xff0c;请参考&#xff1a;MySQL数据库中&#xff0c;如何实现递归查询…

渗透测试——1.2被动扫描

一、概念 目标无法觉察的情况下进行的信息收集。公开渠道可获得的信息&#xff0c;与目标系统不产生直接交互&#xff0c;尽量避免留下一切痕迹。 二、CDN&#xff08;content delivery netword内容分发网路&#xff09; 多台边缘服务器提供网络服务&#xff0c; 三、WAF&am…

CRS-4995: The command ‘start resource’ is invalid in crsctl.

ntp时间调整后&#xff0c;节点1&#xff0c;advm 和acfs offline 处理办法&#xff1a; /u01/app/12.2.0.1/grid/bin/crsctl stop crs /u01/app/12.2.0.1/grid/bin/crsctl start crs 曾经尝试如下命令不起作用 /u01/app/12.2.0.1/grid/bin/acfsload start /u01/app/12.2…

Quartz持久化(springboot整合mybatis版本实现调度任务持久化)--提供源码下载

1、Quartz持久化功能概述 1、实现使用quartz提供的默认11张持久化表存储quartz相关信息。 2、实现定时任务的编辑、启动、关闭、删除。 3、实现自定义持久化表存储quartz定时任务信息。 4、本案例使用springboot整合mybatis框架和MySQL数据库实现持久化 5、提供源码下载 …

Tofu5m目标识别跟踪模块 跟踪模块

Tofu5m 是高性价比目标识别跟踪模块&#xff0c;支持可见光视频或红外网络视频的输入&#xff0c;支持视频下的多类型物体检测、识别、跟踪等功能。 产品支持视频编码、设备管理、目标检测、深度学习识别、跟踪等功能&#xff0c;提供多机版与触控版管理软件&#xff0c;为二次…

cuda加速求解龙格库塔四阶五步积分

一般代码使用cuda加速的方法&#xff1a; 使用PyTorch进行加速&#xff1a; 首先&#xff0c;你需要将你的ODE系统定义为PyTorch模型&#xff0c;这样可以利用PyTorch的自动微分功能和GPU加速。然后&#xff0c;你需要将数据和参数转换为PyTorch张量&#xff0c;并将它们移动到…

Java之AQS(AbstractQueuedSynchronizer)

Java之AQS&#xff08;AbstractQueuedSynchronizer&#xff09; AQS 介绍 AQS 的全称为 AbstractQueuedSynchronizer &#xff0c;翻译过来的意思就是抽象队列同步器。这个类在 java.util.concurrent.locks 包下面。 ● 是用来实现锁或者其他同步器组件的公共基础部分的抽象实…

抖店爆品之后,为什么流量一蹶不振?

我是电商珠珠 做抖店的商家&#xff0c;一般都会遇到在爆品之后&#xff0c;流量出现断崖式下跌的情况。很多商家并不知道是什么原因&#xff0c;觉得平台莫名其妙的。 我做抖店也已经有三年时间了&#xff0c;你们所遇到的问题都是我曾经遇到过的。 所以&#xff0c;出现这…

Mybatis缓存机制详解与实例分析

前言&#xff1a; 本篇文章主要讲解Mybatis缓存机制的知识。该专栏比较适合刚入坑Java的小白以及准备秋招的大佬阅读。 如果文章有什么需要改进的地方欢迎大佬提出&#xff0c;对大佬有帮助希望可以支持下哦~ 小威在此先感谢各位小伙伴儿了&#x1f601; 以下正文开始 Mybat…

2023_Spark_实验三十三:配置Standalone模式Spark3.4.2集群

实验目的&#xff1a;掌握Spark Standalone部署模式 实验方法&#xff1a;基于centos7部署Spark standalone模式集群 实验步骤&#xff1a; 一、下载spark软件 下载的时候下载与自己idea里对应版本的spark News | Apache Spark 选择任意一个下载即可 - spark 3.4.1 - spark …

PTA 最小生成树-kruskal

7-92 最小生成树-kruskal 分数 10 全屏浏览题目 作者 任唯 单位 河北农业大学 题目给出一个无向连通图&#xff0c;要求求出其最小生成树的权值。 温馨提示&#xff1a;本题请使用kruskal最小生成树算法。 输入格式: 输出格式: 输出一个整数表示最小生成树的各边的长度之和。…

通过字符设备驱动点亮板子上的led灯

通过字符设备驱动点亮板子上的led灯 app: test.c char buf[3] 1 0 0 0 1 0 0 0 1 ------------------|------------------------ kernel: led_driver.c -------------------|------------------------ hardware: RGB_led 应用程序如何将数据传递给驱动&#xff08;读写…

MySQL定时备份实现

一、备份数据库 –all-databases 备份所有数据库 /opt/mysqlcopy/all_$(date “%Y-%m-%d %H:%M:%S”).sql 备份地址 docker exec -it 容器名称 sh -c "mysqldump -u root -ppassword --all-databases > /opt/mysqlcopy/all_$(date "%Y-%m-%d %H:%M:%S").sq…

Docker 安装 MySQL5.7 和 MySQL8

文章目录 安装 MySQL5.7拉取镜像前期准备&#xff1a;启动容器 安装MySQL8.0拉取镜像查看镜像前期准备启动容器 安装 MySQL5.7 拉取镜像 docker pull mysql:5.7拉下来镜像后 执行 docker images 此时我们已经有这个镜像了。 前期准备&#xff1a; 在根目录下创建 app &…