算法:常见的链表算法

news2025/1/12 0:57:01

文章目录

  • 链表算法
  • 两数相加
    • 两两交换链表中的节点
    • 重排链表
    • 合并K个升序链表
    • K个一组翻转链表
  • 总结

本篇总结常见的链表算法题和看他人题解所得到的一些收获

链表算法

关于链表的算法:

  1. 画图:画图可以解决绝大部分的数据结构的问题,任何的算法题借助图都可以有一个比较清晰的思路
  2. 引入哨兵位头节点:头节点可以避免处理很多边界情况,在解决一些问题前很方便
  3. 多定义几个变量:可以很方便的解决问题
  4. 快慢指针:在处理带环的问题,环的入口,倒数节点的时候很好用

两数相加

在这里插入图片描述

在这里积累这个题的目的是学习代码的写法,学习他人的代码

这是自己的代码:

class Solution 
{
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) 
    {
        ListNode* newhead = new ListNode();
        ListNode* cur1 = l1;
        ListNode* cur2 = l2;
        ListNode* cur = newhead;
        int carry = 0, sum = 0, ret = 0;

        while(cur1 && cur2)
        {
            // 计算出这个节点要存的值是多少
            sum = cur1->val + cur2->val + carry;
            carry = sum / 10;
            ret = sum % 10;
            
            // 插入节点
            ListNode* newnode = new ListNode(ret);
            cur->next = newnode;
            cur = newnode;

            // 迭代
            cur1 = cur1->next;
            cur2 = cur2->next;
        }

        while(cur1)
        {
            // 计算出这个节点要存的值是多少
            sum = cur1->val + carry;
            carry = sum / 10;
            ret = sum % 10;

            // 插入节点
            ListNode* newnode = new ListNode(ret);
            cur->next = newnode;
            cur = newnode;

            // 迭代
            cur1 = cur1->next;
        }

        while(cur2)
        {
            // 计算出这个节点要存的值是多少
            sum = cur2->val + carry;
            carry = sum / 10;
            ret = sum % 10;

            // 插入节点
            ListNode* newnode = new ListNode(ret);
            cur->next = newnode;
            cur = newnode;

            // 迭代
            cur2 = cur2->next;
        }

        if(carry)
        {
            // 插入节点
            ListNode* newnode = new ListNode(carry);
            cur->next = newnode;
            cur = newnode;
        }

        return newhead->next;
    }
};

运行可以通过,但是代码量比较冗余,其实可以发现while循环和后面的if语句有相当多的相似语句,因此看下面的代码:

class Solution 
{
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) 
    {
        ListNode* newhead = new ListNode();
        ListNode* cur1 = l1;
        ListNode* cur2 = l2;
        ListNode* cur = newhead;
        int sum = 0;

        while(cur1 || cur2 || sum)
        {
            // 先加第一个链表
            if(cur1)
            {
               sum += cur1->val;
               cur1 = cur1->next;
            }

            // 再加第二个链表
            if(cur2)
            {
                sum += cur2->val;
                cur2 = cur2->next;
            }

            // 处理节点数据
            cur->next = new ListNode(sum % 10);
            cur = cur->next;
            sum /= 10;
        }

        // 处理掉不必要的内存空间
        cur = newhead->next;
        delete newhead;
        return cur;
    }
};

两两交换链表中的节点

在这里插入图片描述
其实这个题已经见过很多次了,有很多种解决方法,直接模拟,递归…

但是这里积累它的意义在于,引入虚拟头节点对于解题是很有帮助的

/**
 * 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();
        newhead->next = head;
        ListNode* prev = newhead;
        ListNode* cur = newhead->next;
        ListNode* next = cur->next;
        ListNode* nnext = next->next;
        while(cur && next)
        {
            // 交换节点
            prev->next = next;
            next->next = cur;
            cur->next = nnext;

            // 迭代
            prev = cur;
            cur = prev->next;
            if(cur) next = cur->next;
            if(next) nnext = next->next;
        }

        ListNode* res = newhead->next;
        delete newhead;
        return res;
    }
};

不带虚拟头节点:

/**
 * 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 = head->next;
        // 找到当前待交换的两个节点
        ListNode* left = head;
        ListNode* right = head->next;
        while(left && right)
        {
            // 交换两个节点
            left->next = right->next;
            right->next = left;
            // 进行迭代
            ListNode* prev = left;
            left = left->next;
            if(left)
            {
                right = left->next;
                if(right)
                    prev->next = right;
            }
        }

        return newhead;
    }
};

递归解题

/**
 * 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* left = head;
        ListNode* right = left->next;
        ListNode* tmp = right->next;
        right->next = left;
        left->next = swapPairs(tmp);
        return right;
    }
};

重排链表

在这里插入图片描述
积累本题的意义在于,本题综合了逆序链表,快慢指针,链表插入的问题,是一个综合性的题

/**
 * 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) 
    {
        // 利用快慢双指针找到中间节点
        ListNode* slow = head;
        ListNode* fast = head;
        while(fast && fast->next)
        {
            slow = slow->next;
            fast = fast->next->next;
        }

        // 得到两个新链表
        ListNode* newheadleft = head;
        ListNode* newheadright = Reverse(slow->next);
        slow->next = nullptr;

        // 把这两个链表再组装起来
        ListNode* newhead = new ListNode();
        ListNode* tail = newhead;
        ListNode* cur1 = newheadleft;
        ListNode* cur2 = newheadright;
        while(cur1 || cur2)
        {
            if(cur1)
            {
                tail->next = cur1;
                tail = tail->next;
                cur1 = cur1->next;
            }
            if(cur2)
            {
                tail->next = cur2;
                tail = tail->next;
                cur2 = cur2->next;
            }
        }

        ListNode* res = newhead->next;
        delete newhead;
        head = res;
    }

    ListNode* Reverse(ListNode* head)
    {
        ListNode* newhead = new ListNode();
        ListNode* cur = head;
        while(cur)
        {
            ListNode* next = cur->next;
            cur->next = newhead->next;
            newhead->next = cur;
            cur = next;
        }

        ListNode* res = newhead->next;
        delete newhead;
        return res;
    }
};

合并K个升序链表

在这里插入图片描述

关于这个题,乍一看其实是很难想到用什么方法做的,而实际上如果要是使用优先级队列或者是递归的思想来解题是可以解决的,算法的思路难,但是实际的变现并不难

这里提供三种解题方法:暴力求解、利用优先级队列来解题、利用归并的思想来解题

暴力求解

/**
 * 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) 
    {
        ListNode* newhead = new ListNode();
        ListNode* tail = newhead;
        for(int i = 0; i < lists.size(); i++)
        {
            merge(newhead->next, lists[i]);
        }
        return newhead->next;
    }

    void merge(ListNode*& head, ListNode* cur)
    {
        ListNode* newhead = new ListNode();
        ListNode* tail = newhead;
        while(head && cur)
        {
            if(head->val < cur->val)
            {
                tail->next = head;
                tail = tail->next;
                head = head->next;
            }
            else
            {
                tail->next = cur;
                tail = tail->next;
                cur = cur->next;
            }
        }
        if(head) tail->next = head;
        if(cur) tail->next = cur;

        head = newhead->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:
    struct cmp
    {
        bool operator()(ListNode* l1, ListNode* l2)
        {
            return l1->val > l2->val;
        }
    };
    // 利用优先级队列进行优化
    ListNode* mergeKLists(vector<ListNode*>& lists) 
    {
        priority_queue<ListNode*, vector<ListNode*>, cmp> pq;
        
        // 把每一个链表都塞进去
        for(int i = 0; i <lists.size(); i++)
        {
            if(lists[i])
                pq.push(lists[i]);
        }
        
        // 创建虚拟头节点,开始链入到链表中
        ListNode* newhead = new ListNode();
        ListNode* tail = newhead;

        // 找到最小的节点,拿出来,再把它的下一个节点再塞进去,直到队列为空
        while(!pq.empty())
        {
            ListNode* top = pq.top();
            pq.pop();
            if(top != nullptr)
            {
                ListNode* next = top->next;
                top->next = nullptr;
                // 塞到目标链表中
                tail->next = top;
                tail = tail->next;
                // 再把剩下的部分再塞进去
                if(next)
                    pq.push(next);
            }
        }

        // 返回信息
        ListNode* res = newhead->next;
        delete newhead;
        return res;
    }
};

利用归并的思想解题

/**
 * 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];

        // 拆分数组
        int midi = left + (right - left) / 2;
        // [left, midi][midi + 1, right]
        ListNode* l1 = merge(lists, left, midi);
        ListNode* l2 = merge(lists, midi + 1, right);

        // 排序
        return mergetwonode(l1, l2);
    }

    // 合并链表
    ListNode* mergetwonode(ListNode* l1, ListNode* l2)
    {
        ListNode* newhead = new ListNode();
        ListNode* cur1 = l1, *cur2 = l2, *tail = newhead;
        
        while(cur1 && cur2)
        {
            if(cur1->val <= cur2->val)
            {
                tail->next = cur1;
                cur1 = cur1->next;
                tail = tail->next;
            }
            else
            {
                tail->next = cur2;
                cur2 = cur2->next;
                tail = tail->next;
            }
        }

        if(cur1) tail->next = cur1;
        if(cur2) tail->next = cur2;

        ListNode* res = newhead->next;
        delete newhead;
        return res;
    }
};

K个一组翻转链表

在这里插入图片描述

/**
 * 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 
{
    // 思路:把链表k个一组拿出来,再翻转,再链入一个新的链表中
public:
    ListNode* reverseKGroup(ListNode* head, int k) 
    {
        ListNode* newhead = new ListNode();
        ListNode* tail = newhead;
        ListNode* left = head, *right = left;

        while(left && right)
        {
            // 把链表k个一组分出来
            for(int i = 1; i < k; i++)
            {
                // 如果此时已经到最后了,那么这最后一组就不需要进行翻转
                if(right == nullptr)
                {
                    tail->next = left;
                    return newhead->next;
                }
                right = right->next;
            }
            // 断链
            if(right)
            {
                ListNode* newleft = right->next;
                right->next = nullptr;
                // 现在从[left, right]就是一段完整的不带头节点的链表了
                // 把这段链表进行翻转,并链入到新的链表中
                tail->next = reverse(left);
                tail = left;
                // 迭代,找下一组k个节点
                left = newleft, right = left;
            }
        }
        tail->next = left;
        return newhead->next;
    }

    // 进行链表的反转
    ListNode* reverse(ListNode* cur)
    {
        ListNode* newhead = new ListNode();
        
        // 把链表中的节点头插到新链表中
        while(cur)
        {
            ListNode* next = cur->next;
            cur->next = newhead->next;
            newhead->next = cur;
            cur = next;
        }

        ListNode* res = newhead->next;
        delete newhead;
        return res;
    }
};

这个题的难点在于细节问题的处理,其实思路并不难

总结

其实对于链表这一块的算法主要是体现在需要处理一些边界情况和循环调用带来的问题,通过借助虚拟头节点和多创建几个指针可以缓解这种情况,主要是细节要处理好,对于思维的需求没有那么高

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

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

相关文章

Ubuntu 安装 CUDA 和 cuDNN 详细步骤

我的Linux系统背景&#xff1a; 系统和驱动都已安装。 系统是centos 8。查看自己操作系统的版本信息&#xff1a;cat /etc/issue或者是 cat /etc/lsb-release 用nvidia-smi可以看到显卡驱动和可支持的最高cuda版本&#xff0c;我的是12.2。驱动版本是535.129.03 首先&#…

snakeyaml编辑yaml文件并覆盖注释

文章目录 前言技术积累实战演示1、引入maven依赖2、覆盖注释工具类3、snakeyaml工具类4、测试用例5、测试效果展示 写在最后 前言 最近在做一个动态整合框架的项目&#xff0c;需要根据需求动态组装各个功能模块。其中就涉及到了在application.yaml中加入其他模块的配置&#…

Kubernetes(K8s 1.27.x) 快速上手+实践,无废话纯享版

文章目录 1 基础知识1.1 K8s 有用么&#xff1f;1.2 K8s 是什么&#xff1f;1.3 k8s 部署方式1.4 k8s 环境解析 2 环境部署2.1 基础环境配置2.2 容器环境操作2.3 cri环境操作2.4 harbor仓库操作2.5 k8s集群初始化2.6 k8s环境收尾操作 3 应用部署3.1 应用管理解读3.2 应用部署实…

浏览器提示不安全

当我们使用浏览器访问一个网站时&#xff0c;如果该网站使用的是HTTPS连接&#xff0c;那么浏览器会对其进行安全性的检查。其中一项重要的检查就是确认该网站是否拥有有效的SSL证书。然而&#xff0c;有时我们会在浏览器中看到“不安全”的警告&#xff0c;这通常是由于SSL证书…

【Go自学版】02-goroutine

利用时间片分割进程&#xff0c;致使宏观上A,B,C同时执行&#xff08;并发&#xff09; CPU利用率包含了执行和切换&#xff0c;进程/线程的数量越多&#xff0c;切换成本也会增大 最大并行数&#xff1a;GOMAXPROCS work stealing: 偷其他队列的G hand off: 当前G1阻塞&#…

中缀表达式转后缀表达式与后缀表达式计算(详解)

**中缀表达式转后缀表达式的一般步骤如下&#xff1a; 1&#xff1a;创建一个空的栈和一个空的输出列表。 2&#xff1a;从左到右扫描中缀表达式的每个字符。 3&#xff1a;如果当前字符是操作数&#xff0c;则直接将其加入到输出列表中。 4&#xff1a;如果当前字符是运算符&a…

禅道v11.6 基于linux环境下的docker容器搭建的靶场

一、环境搭建 linux环境下的 在docker环境下安装禅道CMS V11.6 docker run --name zentao_v11.6 -p 8084:80 -v /u01/zentao/www:/app/zentaopms -v /u01/zentao/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD123456 -d docker.io/yunwisdom/zentao:v11.6二、常见问题 1.删除…

软件开发的代码审查工具

在进行软件开发时&#xff0c;代码审查&#xff08;Code Review&#xff09;是一种非常重要的实践&#xff0c;它有助于发现潜在的问题、提高代码质量&#xff0c;并促使团队成员之间的知识共享。有许多工具可用于简化和优化代码审查过程。以下是一些常见的代码审查工具&#x…

力扣541.反转字符串 II

文章目录 力扣541.反转字符串 II示例代码实现总结收获 力扣541.反转字符串 II 示例 代码实现 class Solution {public String reverseStr(String s, int k) {char[] ans s.toCharArray();for(int i0;i<ans.length;i2*k){int begin i;int end Math.min(ans.length-1,begin…

【EI会议征稿】2024年粤港澳大湾区数字经济与人工智能国际学术会议(DEAI2024)

2024年粤港澳大湾区数字经济与人工智能国际学术会议(DEAI2024) 2024 Guangdong-Hong Kong-Macao Greater Bay Area International Conference on Digital Economy and Artificial Intelligence(DEAI2024) 2024年粤港澳大湾区数字经济与人工智能国际学术会议(DEAI2024)由广东科…

Emacs之dired模式重新绑定键值v(一百三十一)

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

搞懂HashTable, HashMap, ConcurrentHashMap 的区别,看着一篇就足够了!!!

&#x1f6e9;️&#x1f6e9;️&#x1f6e9;️ 今天给大家分享的是 HashTable, HashMap, ConcurrentHashMap之间的区别&#xff0c;也是自己学习过程中的总结。 清风的CSDN博客 &#x1f6e9;️&#x1f6e9;️&#x1f6e9;️希望我的文章能对你有所帮助&#xff0c;有不足的…

为什么每个 Java 开发者都需要了解 Scala

前面我们一起回顾了第九期 Scala & Java Meetup 中最受关注的话题 —— jdk 并发编程的终极解决方案&#xff1a;虚拟线程&#xff0c;探讨了这一新特性对包括 Scala 在内的响应式编程语言的影响。 本次 Meetup 的首位分享者 Chunsen&#xff0c;在加入 Tubi 成为 Scala 开…

Linux 进程优先级

什么是进程的优先级 优先级&#xff1a;对资源的访问顺序&#xff01;注意优先级与权限的区别&#xff0c;优先级决定的是访问资源的顺序&#xff0c;这意味着无论是谁都可以访问到资源&#xff1b;但是如果你没有权限&#xff0c;你是不能访问资源的&#xff01; 这个应该比较…

XXL-JOB日志相关报错的原因

1.问题&#xff1a;msg&#xff1a;job handler [myJobHandler] not found. 原因&#xff1a;执行器中没有对应的执行器。 执行器中代码展示&#xff1a; Component Slf4j public class JobHandler {XxlJob(value "abcHandler")public void abcHandler() {log.inf…

卷积神经网络中用1*1 卷积有什么作用或者好处呢?

一、来源&#xff1a;[1312.4400] Network In Network &#xff08;如果11卷积核接在普通的卷积层后面&#xff0c;配合激活函数&#xff0c;即可实现network in network的结构&#xff09; 二、应用&#xff1a;GoogleNet中的Inception、ResNet中的残差模块 三、作用&#x…

【C++】:STL源码剖析之vector类容器的底层模拟实现

&#x1f4da;1.vector接口总览 namespace lyp {//模拟实现vectortemplate<class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;//默认成员函数vector(); //构造函数vector(size_t n, const …

微信小程序 - 创建 ZIP 压缩包

微信小程序 - 创建 ZIP 压缩包 场景分享代码片段导入 JSZip创建ZIP文件追加写入文件测试方法参考资料 场景 微信小程序只提供了解压ZIP的API&#xff0c;并没有提供创建ZIP的方法。 当我们想把自己处理好的保存&#xff0c;打包ZIP保存下来时就需要自己实现了。 分享代码片段…