LeetCode 热题 100之链表3

news2024/11/25 22:29:39

1.k个一组翻转链表

在这里插入图片描述
思路分析:我们需要将链表分成若干个长度为 k 的子链表组,逐组进行翻转。若最后一组节点的数量不足 k,则保持原有顺序

  • 创建一个虚拟头节点 dummy,以简化边界条件的处理。该节点的 next 指向链表的头节点。通过 dummy 节点,可以轻松连接翻转后的链表和未翻转部分,避免对链表头节点的特殊处理。

  • 循环遍历链表:使用while循环不断寻找长度为k的子链表。如果剩余节点不足k,则退出循环。

  • 翻转每组节点

    • 在找到的一组k个节点中,使用三指针法进行翻转
    • prev指针指向当前组后面未翻转部分的第一个节点;
    • node指针指向当前组的第一个节点;
    • nextNode用于暂存node的下一个节点,以便在翻转过程中不断移动
    • 在循环中,逐个节点调整next指针,反转指向关系
  • 连接翻转后的节点

    • 将翻转后的组连接到preGroupEnd(上一组的尾节点)的next指针上;
    • 更新prevGroupEnd为当前组的起始节点,以便为下一组的连接做准备
  • 更新当前节点

    • 将curr更新为当前组的下一个节点,继续寻找下一组进行翻转。

具体实现代码(详解版)

/**
 * 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) {
        ListNode* dummy = new ListNode(0,head);
        if(!head || k == 1) return head;

        ListNode* prevGroupEnd = dummy;//保存上一个翻转组的尾部节点
        ListNode* curr = head;//当前节点

        while(true){
            ListNode* groupStart = curr;//当前组的起始节点
            int count = 0;

            //检查当前剩余节点是否足够组成一组
            while(curr && count < k){
                curr = curr->next;
                count ++;
            }
            
            //不足k个节点,跳出循环,不再翻转
            if(count < k) break;
            //翻转当前的k个节点
            ListNode* prev = curr;
            ListNode* node = groupStart;

            for(int i = 0 ; i < k ; i ++){
                ListNode* nextNode = node->next;
                node->next = prev;
                prev = node;
                node = nextNode;
            }
            //连接上一组和当前组
            prevGroupEnd->next = prev;//当前组的尾部翻转后变为组的头
            prevGroupEnd = groupStart;//当前翻转前的头变成了组的尾部
            curr = groupStart->next;//移动到下一个组的第一个节点
        }
        return dummy->next;
        
    }
};
  • 时间复杂度为 0 ( n ) 0(n) 0(n),空间复杂度为 O ( 1 ) . O(1). O(1).

2.随机链表的复制

在这里插入图片描述
思路分析(回溯+哈希表):可以使用回溯和哈希表的组合方法来实现深拷贝链表。哈希表用于保存原链表节点与其对应的新节点之间的映射关系,从而实现递归回溯创建新节点并设置 next 和 random 指针。

  • 递归回溯
    • 对于每个节点node,检查是否已经创建了它的副本
    • 如果node的副本已经存在(可以在哈希表中找到),则直接返回该副本,避免重复创建;
    • 如果node的副本不存在,创建一个新节点并将其加入哈希表,用于后续访问时直接返回;
  • 哈希表保存映射关系
    • 使用哈希表存储原节点和对应新节点之间的映射关系;
    • 通过该映射关系,可以在递归调用中迅速找到原节点对应的新节点,并在处理 next 和 random 指针时避免重复创建。
  • 递归创建新节点的 next 和 random 指针:
    • 对于新创建的节点,通过递归回溯设置它的 next 和 random 指针,分别指向递归创建的相应位置的节点。

具体实现代码(详解版):

/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* next;
    Node* random;
    
    Node(int _val) {
        val = _val;
        next = NULL;
        random = NULL;
    }
};
*/

class Solution {
public:
    unordered_map<Node*,Node*> nodeMap;
    
    Node* copyRandomList(Node* head) {
        //哈希表,存储原节点和对应新节点的映射
        if(!head) return nullptr;

        //检查哈希表中是否已经存在该节点的副本
        if(nodeMap.find(head) != nodeMap.end()) return nodeMap[head];

        //创建新节点并将其添加到哈希表中
        Node* newNode = new Node(head->val);
        nodeMap[head] = newNode;

        //递归设置next和random指针
        newNode->next = copyRandomList(head->next);
        newNode->random = copyRandomList(head->random);

        return newNode;

    }
};
  • 时间复杂度:O(n),n为节点数
  • 空间复杂度:O(n),使用哈希表保存节点之间的映射关系。

3.排序链表

在这里插入图片描述
思路分析1(先存储再排序):

  • 遍历链表将值存入数组;
  • 然后排序数组;
  • 将排序后的值更新回链表。

具体实现代码(详解版):

class Solution {
public:
    ListNode* sortList(ListNode* head) {
        ListNode* tmp = head;
        vector<int> arr;
        while(tmp) {
            arr.push_back(tmp -> val);
            tmp = tmp -> next;
        }
        sort(arr.begin(), arr.end());
        int i;
        for(i = 0, tmp = head;i < arr.size(), tmp != nullptr; i++, tmp = tmp -> next) {
            tmp -> val = arr[i];
        }
        return head;
    }
};

2.思路分析2(归并排序)

  • 递归分割链表
    • 使用快慢指针找到链表的中间位置,slow最终停在链表的中点,fast到链表末尾
    • 将链表从中间位置断开,形式左右两部分进行递归排序
  • 合并两个已排序链表
    • 使用辅助函数 mergeTwoLists,按升序合并两个已排序链表。
    • dummy 节点帮助简化边界处理逻辑,最终返回 dummy.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* sortList(ListNode* head) {
        // 基本情况:如果链表为空或只有一个节点,则已经有序,直接返回
        if (!head || !head->next) return head;

        // 第一步:使用快慢指针将链表分成两半
        ListNode* slow = head;
        ListNode* fast = head;
        ListNode* prev = nullptr;

        // 使用快慢指针寻找链表的中间节点
        while (fast && fast->next) {
            prev = slow;
            slow = slow->next;
            fast = fast->next->next;
        }

        // 将链表分成两部分
        prev->next = nullptr;

        // 递归排序每一半
        ListNode* left = sortList(head);
        ListNode* right = sortList(slow);

        // 合并两个已排序的链表
        return mergeTwoLists(left, right);
    }

private:
    // 辅助函数:合并两个已排序的链表
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        ListNode dummy(0);  // 哑节点,方便操作
        ListNode* tail = &dummy;

        // 合并两个链表
        while (l1 && l2) {
            if (l1->val < l2->val) {
                tail->next = l1;  // 将 l1 的当前节点连接到合并后的链表中
                l1 = l1->next;    // 移动 l1 到下一个节点
            } else {
                tail->next = l2;  // 将 l2 的当前节点连接到合并后的链表中
                l2 = l2->next;    // 移动 l2 到下一个节点
            }
            tail = tail->next;    // 移动 tail 到合并后的链表的最后一个节点
        }

        // 将剩余的节点(如果有)连接到合并后的链表中
        tail->next = l1 ? l1 : l2;

        return dummy.next;  // 返回合并后的链表头节点
    }
};

4.合并4个升序链表

**思路分析1(暴力顺序合并):**我们可以想到一种最朴素的方法:用一个变量 res 来维护以及合并的链表,第 i 次循环把第 i 个链表和 res 合并,答案保存到 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:
    // 合并 k 个已排序链表
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        ListNode* res = nullptr;  // 初始化合并后的链表为 nullptr
        // 遍历每一个链表,并依次合并
        for(size_t i = 0; i < lists.size(); i++) {
            res = mergeTwoLists(res, lists[i]);  // 合并当前链表与已合并的链表
        }
        return res;  // 返回合并后的链表
    }

private:
    // 辅助函数:合并两个已排序的链表
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        ListNode dummy(0);  // 哑节点,方便操作,避免处理空链表时的特殊情况
        ListNode* tail = &dummy;  // 指向合并链表的最后一个节点

        // 合并两个链表
        while (l1 && l2) {  // 当两个链表都不为空时
            if (l1->val < l2->val) {
                tail->next = l1;  // 将 l1 的当前节点连接到合并后的链表中
                l1 = l1->next;    // 移动 l1 到下一个节点
            } else {
                tail->next = l2;  // 将 l2 的当前节点连接到合并后的链表中
                l2 = l2->next;    // 移动 l2 到下一个节点
            }
            tail = tail->next;  // 移动 tail 到合并后的链表的最后一个节点
        }

        // 将剩余的节点(如果有)连接到合并后的链表中
        tail->next = l1 ? l1 : l2;  // 如果 l1 还有剩余,则连接 l1,反之连接 l2

        return dummy.next;  // 返回合并后的链表头节点
    }
};

思路分析2(使用优先队列)

  • 使用优先队列:使用优先队列(最小堆)来保持每个链表的头节点,确保我们总是可以快速获取当前最小的节点。
  • 初始化优先队列
    • 将每个链表的头节点放入优先队列中。这样,优先队列中将始终保持所有待处理的节点,并且可以通过堆的特性来获取最小的节点
  • 合并链表
    • 创建一个虚拟头节点 dummy,用于方便处理合并链表的操作。通过一个 tail 指针来跟踪合并链表的最后一个节点。
    • 反复从优先队列中取出最小的节点,并将其添加到合并链表中。
    • 如果取出的节点有下一个节点,则将其下一个节点也放入优先队列中。这样,优先队列中始终保留当前各链表的待处理节点。
  • 返回结果
    • 合并完成之和,返回虚拟头节点 dummy 的下一个节点,即合并链表的头节点

具体实现代码(详解版):

class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        // 定义一个优先队列,按节点值排序
        auto cmp = [](ListNode* a, ListNode* b) { return a->val > b->val; };
        priority_queue<ListNode*, vector<ListNode*>, decltype(cmp)> minHeap(cmp);

        // 将每个链表的头节点放入优先队列中
        for (ListNode* list : lists) {
            if (list) {
                minHeap.push(list);
            }
        }

        // 创建一个虚拟头节点,以便于处理合并链表
        ListNode dummy(0);
        ListNode* tail = &dummy;

        // 合并过程
        while (!minHeap.empty()) {
            // 取出最小的节点
            ListNode* minNode = minHeap.top();
            minHeap.pop();

            // 将该节点连接到合并链表中
            tail->next = minNode;
            tail = tail->next;

            // 如果该节点有下一个节点,则将其加入优先队列
            if (minNode->next) {
                minHeap.push(minNode->next);
            }
        }

        return dummy.next;  // 返回合并后的链表头节点
    }
};

5.LRU缓存

在这里插入图片描述
具体实现代码(详解版):

// 定义双向链表节点结构体
struct DLinkedNode {
    int key, value;                // 节点的键和值
    DLinkedNode* prev;            // 指向前一个节点的指针
    DLinkedNode* next;            // 指向下一个节点的指针
    
    // 默认构造函数
    DLinkedNode(): key(0), value(0), prev(nullptr), next(nullptr) {}
    
    // 带参数的构造函数
    DLinkedNode(int _key, int _value): key(_key), value(_value), prev(nullptr), next(nullptr) {}
};

// 定义 LRUCache 类
class LRUCache {
private:
    unordered_map<int, DLinkedNode*> cache; // 哈希表,存储键与双向链表节点的映射
    DLinkedNode* head;                      // 伪头部节点
    DLinkedNode* tail;                      // 伪尾部节点
    int size;                               // 当前缓存的大小
    int capacity;                           // 缓存的最大容量

public:
    // 构造函数,初始化缓存容量
    LRUCache(int _capacity): capacity(_capacity), size(0) {
        // 创建伪头部和伪尾部节点
        head = new DLinkedNode();
        tail = new DLinkedNode();
        head->next = tail;   // 头部的下一个指向尾部
        tail->prev = head;   // 尾部的前一个指向头部
    }
    
    // 获取缓存中键对应的值
    int get(int key) {
        if (!cache.count(key)) {
            return -1;  // 如果键不存在,返回 -1
        }
        // 如果键存在,找到对应节点并移动到头部
        DLinkedNode* node = cache[key];
        moveToHead(node);   // 将该节点移动到链表头部
        return node->value; // 返回节点的值
    }
    
    // 插入或更新缓存中的键值对
    void put(int key, int value) {
        if (!cache.count(key)) {  // 如果键不存在
            // 创建一个新的节点
            DLinkedNode* node = new DLinkedNode(key, value);
            // 将节点添加到哈希表
            cache[key] = node;
            // 添加到双向链表的头部
            addToHead(node);
            ++size; // 增加缓存大小
            
            // 如果超出容量,删除双向链表的尾部节点
            if (size > capacity) {
                DLinkedNode* removed = removeTail(); // 删除尾部节点
                cache.erase(removed->key);           // 从哈希表中移除
                delete removed;                       // 释放内存
                --size;                              // 减小缓存大小
            }
        } else {  // 如果键已存在
            DLinkedNode* node = cache[key];
            node->value = value; // 更新节点的值
            moveToHead(node);    // 将该节点移动到链表头部
        }
    }

    // 将节点添加到双向链表的头部
    void addToHead(DLinkedNode* node) {
        node->prev = head;                   // 设置节点的前驱为头部
        node->next = head->next;             // 设置节点的后继为头部的下一个节点
        head->next->prev = node;              // 将头部的下一个节点的前驱指向新节点
        head->next = node;                    // 将头部的下一个节点指向新节点
    }
    
    // 从双向链表中移除指定节点
    void removeNode(DLinkedNode* node) {
        node->prev->next = node->next;      // 将前驱节点的后继指向当前节点的后继
        node->next->prev = node->prev;      // 将后继节点的前驱指向当前节点的前驱
    }

    // 将指定节点移动到双向链表的头部
    void moveToHead(DLinkedNode* node) {
        removeNode(node); // 移除节点
        addToHead(node);  // 将节点添加到头部
    }

    // 移除双向链表的尾部节点
    DLinkedNode* removeTail() {
        DLinkedNode* node = tail->prev;  // 获取尾部前一个节点
        removeNode(node);                 // 从链表中移除该节点
        return node;                      // 返回被移除的节点
    }
};

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

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

相关文章

Redis慢查询分析优化

文章目录 一、定义二、慢查询参数配置三、慢查询日志四、排查步骤五、Redis变慢原因 一、定义 在Redis执行时耗时超过某个阈值的命令&#xff0c;称为慢查询。 慢查询日志帮助开发和运维人员定位系统存在的慢操作。慢查询日志就是系统在命令执行前后计算每条命令的执行时间&…

接口自动化测试平台项目环境搭建

这篇文章主要记录开发接口自动化测试平台的尝试作---环境搭建和写一个项目管理模型。 电脑需要有python环境&#xff0c;且已经安装了django&#xff0c;我用的是python3.12 和 django 最新版本。写代码我使用的pycharm。 其中环境搭建大概分下面几步&#xff1a; 一、在代码…

Oracle OCP认证考试考点详解082系列06

题记&#xff1a; 本系列主要讲解Oracle OCP认证考试考点&#xff08;题目&#xff09;&#xff0c;适用于19C/21C,跟着学OCP考试必过。 26. 第26题&#xff1a; 题目 解析及答案&#xff1a; 关于间隔&#xff08;INTERVAL&#xff09;数据类型&#xff0c;以下哪两个陈述是…

python的数据结构列表方法及扩展(栈和队列)

python的数据结构 python的list方法 list.append() 添加一个元素到列表末尾。list,append(num)相当于a[len(a):] [num] a [1,2,3,4,5] a.append(6) print(a) a[len(a):] [7] print(a)list.extend() 添加指定列表的所有元素。list.extend(nums)相当于a a nums a [1,2,3]…

highcharts的datalabels标签格式化

Highcharts的数据标签格式化 代码如下 plotOptions: {series: {dataLabels: {enabled: true,format: {y:.2f} mm}} },y就是当前数据点的值&#xff0c;.2f代表2位小数&#xff0c;效果如下图

如何安装自动化测试工具katalon?

一、下载&#xff0c;最下面有免费的版本 Katalon Studio 免费下载 |卡塔隆 二、安装 第一次安装&#xff0c;需要输入注册信息&#xff0c;注册成功之后&#xff0c;就可以使用了。

从简单的demo开始让您逐步了解GetX的用法

目录 前言 一、从demo开始体现下Getx的用法 二、从最简单的功能开始 1.新建一个Flutter工程 2.GetX初体验 1.路由跳转 1.普通路由跳转 2.跳转并从堆栈中销毁当前页面 3.跳转并销毁之前所有页面 4.跳转以及传值 2.更方便的实现SnackBar、Dialog、BottomSheet 三、Ge…

【无标题】从网红长沙看背后的湘菜产业

“吃什么&#xff1f;” 相信这是每一个来长沙旅游的人&#xff0c;面临的第一个问题。 近年来&#xff0c;长沙以美食为媒介&#xff0c;成功吸引了无数游客的目光。而湘菜&#xff0c;作为湖南最具特色的美食名片&#xff0c;无疑在这场美食盛宴中占据了举足轻重的地位。 …

使用 Qt 实现自定义罗盘控件

用 Qt 编写一个简单的罗盘控件&#xff0c;该控件能够动态显示方向。该控件实现了一个带有北&#xff08;N&#xff09;和南&#xff08;S&#xff09;标记的圆形罗盘面盘&#xff0c;具有可以根据输入角度旋转的指针。 代码功能概述 该项目定义了一个 CompassWidget 类&…

算法|牛客网华为机试21-30C++

牛客网华为机试 上篇&#xff1a;算法|牛客网华为机试10-20C 文章目录 HJ21 简单密码HJ22 汽水瓶HJ23 删除字符串中出现次数最少的字符HJ24 合唱队HJ25 数据分类处理HJ26 字符串排序HJ27 查找兄弟单词HJ28 素数伴侣HJ29 字符串加解密HJ30 字符串合并处理 HJ21 简单密码 题目描…

使用 MMDetection 实现 Pascal VOC 数据集的目标检测项目练习(二) ubuntu的下载安装

首先&#xff0c;Linux系统是人工智能和深度学习首选系统。原因如下: 开放性和自由度&#xff1a;Linux 是一个开源操作系统&#xff0c;允许开发者自由修改和分发代码。这在开发和研究阶段非常有用&#xff0c;因为开发者可以轻松地访问和修改底层代码。社区支持&#xff1a;…

【ECMAScript标准】深入解析ES5:现代JavaScript的基石

&#x1f9d1;‍&#x1f4bc; 一名茫茫大海中沉浮的小小程序员&#x1f36c; &#x1f449; 你的一键四连 (关注 点赞收藏评论)是我更新的最大动力❤️&#xff01; &#x1f4d1; 目录 &#x1f53d; 前言1️⃣ ES5的概述2️⃣ ES5的关键特性3️⃣ ES5与之前版本的区别4️⃣ …

【万户软件-注册安全分析报告-无验证方式导致安全隐患】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 1. 暴力破解密码&#xff0c;造成用户信息泄露 2. 短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉 3. 带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造…

第2章 Android App开发基础

第 2 章 Android App开发基础 bilibili学习地址 github代码地址 本章介绍基于Android系统的App开发常识&#xff0c;包括以下几个方面&#xff1a;App开发与其他软件开发有什么不一 样&#xff0c;App工程是怎样的组织结构又是怎样配置的&#xff0c;App开发的前后端分离设计…

文本分段Chunking综述-RAG

为什么要分段&#xff1f; 即便大模型开始普通支持更大的上下文&#xff0c;但 RAG 技术目前仍然具有不可替代的价值&#xff0c;RAG 需要外部知识库。外部知识文档往往比较长&#xff0c;可能是包含几十页甚至数百页的内容&#xff0c;如果直接使用会存在以下问题&#xff1a…

R语言 | paletteer包:拥有2100多个调色板!

看到 PMID:39024031 文章的代码中&#xff0c;有颜色设置的语句&#xff1a; pal <- paletteer_d("ggsci::category20_d3")[c(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18)]DimPlot(MM,reduction umap,group.by "sample",label F,pt.size 0.1,c…

怎么看AI大模型(LLM)、智能体(Agent)、知识库、向量数据库、知识图谱,RAG,AGI 的不同形态?

前言 在 AI 人工智能时代&#xff0c;智能体将会是未来最主流的大模型应用方式&#xff1f;人人都有机会通过智能体&#xff0c;解锁成为【超级个体】。 在人工智能的快速发展中&#xff0c;LLM、Agent、知识库、向量数据库、RAG&#xff08;检索增强生成&#xff09;、知识图…

照片不完整?来试试智能扩图,简直不要太满意!(不是广告)

生活中有些照片拍过之后&#xff0c;当时觉得很满意&#xff0c;但过段时间就恨当初没有拍一张完整的图片&#xff01; ——来自小白的感慨 当时跟家里的叮当一起去旅游&#xff0c;我给他拍了一张好看的照片&#xff1a; 今天这张照片如果是整图就好了&#xff01;好气哦&am…

idea连接数据库出现错误的解决方式

在使用idea连接数据库时&#xff0c;出现错误&#xff1a; The server has terminated the handshake. The protocol list option (enabledTLSProtocols) is set, this option might cause connection issues with some versions of MySQL. Consider removing the protocol li…