【LeetCode】链表精选11题

目录

快慢指针:

1. 相交链表(简单)

2. 环形链表(简单)

3. 快乐数(简单)

4. 环形链表 II(中等)

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

递归迭代双解法:

1. 合并两个有序链表(简单)

1.1 递归求解

1.2 迭代求解

2. 反转链表(简单)

2.1 递归求解

2.2 迭代求解

3. 两两交换链表中的节点(中等)

3.1 递归求解

3.2 迭代求解

4. 合并 K 个升序链表(困难)

4.1 递归解法

4.2 迭代解法

综合题:

1. 重排链表(中等)

2. K个一组翻转链表(困难)


快慢指针:

1. 相交链表(简单)

找两个链表的尾结点,尾结点不相同则不相交。假设相交,长短链表之间差距gap步。假设i指向长链表的头节点,j指向短链表的头节点,i先走gap步,然后ij同时走,每次走1步。当ij相遇时,相遇点就是相交点。

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        // 找两个链表的尾结点,尾结点不相同则不相交
        ListNode* tailA = headA;
        ListNode* tailB = headB;
        int lenA = 0;
        int lenB = 0;
        while (tailA->next)
        {
            ++lenA;
            tailA = tailA->next;
        }
        while (tailB->next)
        {
            ++lenB;
            tailB = tailB->next;
        }
        if (tailA != tailB)
            return nullptr;

        // 判断长短链表
        ListNode* longList = headA;
        ListNode* shortList = headB;
        if (lenB > lenA)
        {
            longList = headB;
            shortList = headA;
        }

        // 长链表先走gap步
        int gap = abs(lenA - lenB);
        while (gap--)
        {
            longList = longList->next;
        }
        
        // 同时走,找交点
        while (longList != shortList)
        {
            longList = longList->next;
            shortList = shortList->next;
        }
        return longList;
    }
};

2. 环形链表(简单)

慢指针每次走1步,快指针每次走2步,慢指针进环后,快指针一定能追上慢指针,它们会在环中某点相遇。

为什么慢指针每次走1步,快指针要每次走2步,它们才能相遇?

假设慢指针进环时,快慢指针之间差距gap步。

如果快指针每次走2步,每走一次,它们之间的差距减1,gap一定会减到0。

如果快指针每次走3步,每走一次,它们之间的差距减2。如果gap为偶数,gap一定会减到0。如果gap为奇数,gap会减到-1,表示它们之间的距离变成C - 1(C是环的周长),如果C - 1是偶数,它们会相遇,如果C - 1是奇数,它们永远不会相遇。

class Solution {
public:
    bool hasCycle(ListNode *head) {
        ListNode* slow = head;
        ListNode* fast = head;
        while (fast && fast->next)
        {
            slow = slow->next;
            fast = fast->next->next;
            if (slow == fast)
                return true;
        }
        return false;
    }
};

3. 快乐数(简单)

不是链表题,但是和上一题“环形链表”类似,慢指针每次走1步,快指针每次走2步,慢指针进环后,快指针一定能追上慢指针,它们会在环中某点相遇。如果相遇点为1,则为快乐数,否则不是快乐数。这里的指针表示的是值本身。

class Solution {
public:
    bool isHappy(int n) {
        int slow = n;
        int fast = bitSquareSum(n);
        while (slow != fast)
        {
            slow = bitSquareSum(slow);
            fast = bitSquareSum(bitSquareSum(fast));
        }
        return slow == 1;
    }

private:
    // 计算n的每一位的平方和
    int bitSquareSum(int n)
    {
        int sum = 0;
        while (n)
        {
            int tmp = n % 10;
            sum += tmp * tmp;
            n /= 10;
        }
        return sum;
    }
};

4. 环形链表 II(中等)

慢指针每次走1步,快指针每次走2步,慢指针进环后,快指针一定能追上慢指针,它们会在环中某点相遇。

假设在相遇点,慢指针一共走了k步,那么快指针一定一共走了2k步,所以快指针比慢指针多走了k步。另外,在相遇点,快指针一定比慢指针在环中多走了若干圈。所以,k一定是环的周长(环中节点个数)的整数倍。

此时,让i指向相遇点,j指向链表头节点,它们之间差距k步(慢指针走过的步数),如果i到达了环的入口,j也一定到达了环的入口,因为它们之间差距k步,k一定是环的周长的整数倍。

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode* fast = head;
        ListNode* slow = head;
        while (fast && fast->next)
        {
            fast = fast->next->next;
            slow = slow->next;
            if (fast == slow) // 相遇
            {
                ListNode* i = slow; // 相遇点
                ListNode* j = head;
                while (i != j)
                {
                    i = i->next;
                    j = j->next;
                }
                return i;
            }
        }
        return nullptr;
    }
};

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

快指针先走n步,然后快慢指针同时走,每次走1步。当快指针指向最后一个节点时,慢指针指向倒数第n + 1个节点。

例如,删除链表的倒数第2个节点:

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* preHead = new ListNode(0, head); // 哨兵节点
        ListNode* fast = preHead; // 快指针
        ListNode* slow = preHead; // 慢指针
        // 快指针先走n步
        while (n--)
        {
            fast = fast->next;
        }
        // 快慢指针同时走,每次走1步,直到快指针走到最后一个节点停止
        while (fast->next)
        {
            fast = fast->next;
            slow = slow->next;
        }
        // 此时慢指针指向倒数第n+1个节点
        // 让倒数第n+1个节点的next域直接指向倒数第n-1个节点
        slow->next = slow->next->next;
        return preHead->next;
    }
};

递归迭代双解法:

1. 合并两个有序链表(简单)

1.1 递归求解

重复的子问题——函数头设计

ListNode* mergeTwoLists(ListNode* list1, ListNode* list2)

子问题在做什么——函数体设计

选择两个链表的头节点中值较小的那一个作为最终合并的新链表的头节点,然后将剩下的链表交给递归函数去处理。

  1. 比较list1->val和list2->val的大小(假设list1->val较小)
  2. list1->next = mergeTwoLists(list1->next, list2);
  3. return list1;

递归出口

当某一个链表为空的时候,返回另外一个链表。

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        if (list1 == nullptr)
            return list2;
        if (list2 == nullptr)
            return list1;

        if (list1->val < list2->val)
        {
            list1->next = mergeTwoLists(list1->next, list2);
            return list1;
        }
        else
        {
            list2->next = mergeTwoLists(list1, list2->next);
            return list2;
        }
    }
};

1.2 迭代求解

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        ListNode* preHead = new ListNode; // 哨兵节点
        ListNode* tail = preHead;

        // 取小的尾插
        while (list1 && list2)
        {
            if (list1->val < list2->val)
            {
                tail->next = list1;
                tail = tail->next;
                list1 = list1->next;
            }
            else
            {
                tail->next = list2;
                tail = tail->next;
                list2 = list2->next;
            }
        }

        if (list1)
        {
            tail->next = list1;
        }
        if (list2)
        {
            tail->next = list2;
        }

        return preHead->next;
    }
};

2. 反转链表(简单)

2.1 递归求解

重复的子问题——函数头设计

ListNode* reverseList(ListNode* head)

子问题在做什么——函数体设计

将当前结点之后的链表反转,然后把当前结点添加到反转后的链表后面即可,返回反转后的头节点。

  1. ListNode* newHead = reverseList(head->next);
  2. head->next->next = head;    head->next = nullptr;
  3. return newHead;

递归出口

当前结点为空或者当前只有一个结点的时候,不用反转,直接返回。

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if (head == nullptr || head->next == nullptr)
            return head;

        ListNode* newHead = reverseList(head->next);
        head->next->next = head;
        head->next = nullptr;
        return newHead;
    }
};

2.2 迭代求解

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* pre = nullptr;
        ListNode* cur = head;
        while (cur)
        {
            ListNode* next = cur->next;
            cur->next = pre;
            pre = cur;
            cur = next;
        }

        return pre;
    }
};

3. 两两交换链表中的节点(中等)

3.1 递归求解

重复的子问题——函数头设计

ListNode* swapPairs(ListNode* head)

子问题在做什么——函数体设计

将从第三个节点开始的链表两两交换节点,然后再把前两个节点交换一下,链接上刚才处理过的链表,并返回。

  1. ListNode* tmp = swapPairs(head->next->next);
  2. ListNode* newHead = head->next;    newHead->next = head;
  3. head->next = tmp;
  4. return newHead;

递归出口

当前结点为空或者当前只有一个结点的时候,不用两两交换,直接返回。

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        if (head == nullptr || head->next == nullptr)
            return head;

        ListNode* tmp = swapPairs(head->next->next);
        ListNode* newHead = head->next;
        newHead->next = head;
        head->next = tmp;
        return newHead;
    }
};

3.2 迭代求解

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        ListNode* preHead = new ListNode(0, head); // 哨兵节点
        ListNode* cur = preHead;
        // cur后面的两个节点交换
        while (cur->next && cur->next->next)
        {
            ListNode* node1 = cur->next;
            ListNode* node2 = cur->next->next;
            cur->next = node2;
            node1->next = node2->next;
            node2->next = node1;
            cur = node1;
        }
        return preHead->next;
    }
};

4. 合并 K 个升序链表(困难)

4.1 递归解法

分治的思想,类似归并排序:

  1. 划分两个子区间

  2. 分别对两个子区间的链表进行合并,形成两个有序链表

  3. 合并两个有序链表

重复的子问题——函数头设计

ListNode* merge(vector<ListNode*>& lists, int begin, int end)

子问题在做什么——函数体设计

  1. 划分两个子区间:int mid = (begin + end) / 2;
  2. 递归合并两个子区间:
    ListNode* l1 = merge(lists, begin, mid);
    ListNode* l2 = merge(lists, mid + 1, end);
  3. 合并两个有序链表:return mergeTowList(l1, l2);

递归出口

当区间只有一个链表时,不合并。另外,当题目给出空链表时,不合并。

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

private:
    ListNode* merge(vector<ListNode*>& lists, int begin, int end)
    {
        if (begin > end)
            return nullptr;
        if (begin == end)
            return lists[begin];

        int mid = (begin + end) / 2;
        ListNode* l1 = merge(lists, begin, mid);
        ListNode* l2 = merge(lists, mid + 1, end);
        return mergeTwoLists(l1, l2);
    }
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2)
    {
        ListNode* preHead = new ListNode; // 哨兵节点
        ListNode* tail = preHead;

        // 取小的尾插
        while (list1 && list2)
        {
            if (list1->val < list2->val)
            {
                tail->next = list1;
                tail = tail->next;
                list1 = list1->next;
            }
            else
            {
                tail->next = list2;
                tail = tail->next;
                list2 = list2->next;
            }
        }

        if (list1)
        {
            tail->next = list1;
        }
        if (list2)
        {
            tail->next = list2;
        }

        return preHead->next;
    }
};

4.2 迭代解法

和“合并两个有序链表”类似,就是取小的尾插。怎么判断K个链表未合并的头节点中最小的那个?利用堆这个数据结构即可。把K个链表未合并的头节点放进一个小根堆,堆顶就是最小的那个。

class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        // 创建小根堆
        priority_queue<ListNode*, vector<ListNode*>, cmp> heap;
        // 将所有头节点放进小根堆
        for (auto& l : lists)
        {
            if (l)
            {
                heap.push(l);
            }
        }
        // 合并链表
        ListNode* preHead = new ListNode; // 哨兵节点
        ListNode* tail = preHead;
        while (!heap.empty())
        {
            // 取堆顶节点尾插
            tail->next = heap.top();
            heap.pop();
            tail = tail->next;
            // 将刚才合并的节点的下一个节点补充进堆
            if (tail->next)
            {
                heap.push(tail->next);
            }
        }
        return preHead->next;
    }

private:
    struct cmp
    {
        bool operator()(ListNode* n1, ListNode* n2)
        {
            return n1->val > n2->val;
        }
    };
};

综合题:

1. 重排链表(中等)

把链表后半段反转,再合并起来:

链表长度是偶数:1 2 3 4    (2是中间节点)

1 2

4 3

合并起来:1 4 2 3

链表长度是奇数:1 2 3 4 5    (3是中间节点)

1 2 3

5 4(4 5反转)

合并起来:1 5 2 4 3

class Solution {
public:
    void reorderList(ListNode* head) {
        ListNode* mid = midNode(head);
        ListNode* l2 = reverseList(mid->next);
        mid->next = nullptr;
        ListNode* l1 = head;
        mergeLists(l1, l2);
    }

private:
    // 快慢指针找链表的中间节点,如果节点个数为偶数,取靠左的
    ListNode* midNode(ListNode* head)
    {
        ListNode* fast = head;
        ListNode* slow = head;
        // 慢指针每次走1步,快指针每次走2步
        // 如果节点个数为奇数,当快指针指向最后一个节点时,慢指针指向中间节点
        // 如果节点个数为奇数,当快指针指向倒数第二个节点时,慢指针指向靠左的中间节点
        while (fast->next && fast->next->next)
        {
            fast = fast->next->next;
            slow = slow->next;
        }
        return slow;
    }
    // 反转链表
    ListNode* reverseList(ListNode* head) {
        ListNode* pre = nullptr;
        ListNode* cur = head;
        while (cur)
        {
            ListNode* next = cur->next;
            cur->next = pre;
            pre = cur;
            cur = next;
        }
        return pre;
    }
    // 合并链表
    void mergeLists(ListNode* l1, ListNode* l2)
    {
        ListNode* cur1 = l1;
        ListNode* cur2 = l2;
        while (cur1 && cur2)
        {
            ListNode* next1 = cur1->next;
            ListNode* next2 = cur2->next;
            cur1->next = cur2;
            cur2->next = next1;
            cur1 = next1;
            cur2 = next2;
        }
    }
};

2. K个一组翻转链表(困难)

头插法。

class Solution {
public:
    ListNode* reverseKGroup(ListNode* head, int k) {
        // 求出需要翻转多少组
        int n = 0;
        ListNode* cur = head;
        while (cur)
        {
            cur = cur->next;
            n++;
        }
        n /= k;

        // 重复n次:长度为k的链表翻转
        ListNode* preHead = new ListNode; // 哨兵节点
        ListNode* pre = preHead;
        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 = pre->next;
                pre->next = cur;
                cur = next;
            }
            pre = tmp;
        }

        // 把不需要翻转的部分接上
        pre->next = cur;
        
        return preHead->next;
    }
};

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

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

相关文章

量化投资策略的评估标准及其计算公式

收益率指标&#xff1a;分为策略的总收益率和策略的年化收益率 策略的总收益率&#xff1a; 策略的总收益率是评价一个策略盈利能力的最基本的指标&#xff0c;其计算方法为&#xff1a; 公式中Vt表示策略最终的股票和现金的总价值&#xff0c;V0表示策略最初的股票和现金的总…

探秘JDK 13的黑科技:新特性一览

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 探秘JDK 13的黑科技&#xff1a;新特性一览 前言switch表达式扩展Switch表达式的基本概念&#xff1a;使用Switch表达式的优势&#xff1a;示例代码&#xff1a;注意事项和最佳实践&#xff1a; Text …

Spring Cloud + Vue前后端分离-第7章 核心业务功能开发

Spring Cloud Vue前后端分离-第7章 核心业务功能开发 7-1 课程管理功能开发 课程管理页面美化 1.课程管理页面美化 demo-course.jpg 复制search.html中的部分代码 course.vue 看效果 测试一下新增修改删除效果 1.课程管理页面美化2 scoped:style下的样式只应用于当前组件…

LCT(link cut tree) 详细图解与应用

樱雪喵用时 3days 做了 ybtoj 的 3 道例题&#xff0c;真是太有效率了&#xff01;&#xff01;1 为了避免自己没学明白就瞎写东西误人子弟&#xff0c;这篇 Blog 拖到了现在。 图片基本沿用 OIwiki&#xff0c;原文跳步骤&#xff08;主要是 access 部分&#xff09;的就自己…

Spark编程实验三:Spark SQL编程

目录 一、目的与要求 二、实验内容 三、实验步骤 1、Spark SQL基本操作 2、编程实现将RDD转换为DataFrame 3、编程实现利用DataFrame读写MySQL的数据 四、结果分析与实验体会 一、目的与要求 1、通过实验掌握Spark SQL的基本编程方法&#xff1b; 2、熟悉RDD到DataFram…

如何利用flume进行日志采集

介绍 Apache Flume 是一个分布式、可靠、高可用的日志收集、聚合和传输系统。它常用于将大量日志数据从不同的源&#xff08;如Web服务器、应用程序、传感器等&#xff09;收集到中心化的存储或数据处理系统中。 基本概念 Agent&#xff08;代理&#xff09;&#xff1a; …

039、转置卷积

之——增大高宽 杂谈 通常来说&#xff0c;卷积不会增大输入的高宽&#xff0c;通常要么不变&#xff0c;要么减半&#xff1b;如果想要直接padding来增加高宽&#xff0c;在不断的卷积过程中&#xff0c;padding的0越来越多&#xff0c;最后要做像素级的判断时候&#xff0c;由…

分布式核心技术之分布式共识

文章目录 什么是分布式共识&#xff1f;分布式共识方法PoWPoSDPoS 三种分布式共识算法对比分析 选主过程就是一个分布式共识问题&#xff0c;因为每个节点在选出主节点之前都可以认为自己会成为主节点&#xff0c;也就是说集群节点“存异”&#xff1b;而通过选举的过程选出主节…

基于Java SSM框架实现医院挂号上班打卡系统项目【项目源码+论文说明】计算机毕业设计

基于java的SSM框架实现医院挂号上班打卡系统演示 摘要 在网络发展的时代&#xff0c;国家对人们的健康越来越重视&#xff0c;医院的医疗设备更加先进&#xff0c;医生的医术、服务水平也不断在提高&#xff0c;给用户带来了很大的选择余地&#xff0c;而且人们越来越追求更个…

【leetcode100-019】【矩阵】螺旋矩阵

【题干】 给你一个 m 行 n 列的矩阵 matrix &#xff0c;请按照 顺时针螺旋顺序 &#xff0c;返回矩阵中的所有元素。 【思路】 不难注意到&#xff0c;每进行一次转向&#xff0c;都有一行/列被输出&#xff08;并失效&#xff09;&#xff1b;既然已经失效&#xff0c;那我…

MyBatis-Plus中默认方法对应的SQL到底长啥样?

我希望成为&#xff1a;自媒体圈中技术最好、实战经验最丰富的达人&#xff0c;技术圈中最会分享的架构师。加油&#xff01; 我的公众号&#xff1a;Hoeller 过段时间要给公司同事做Mybatis-Plus相关的培训&#xff0c;所以抓紧时间看看Mybatis-Plus的源码&#xff0c;顺便也分…

RT-Thread 内核对象管理框架

内核对象管理框架 RT-Thread采用内核对象管理系统来访问/管理所有内核对象&#xff0c;内核对象包含了内核中绝大部分设施&#xff0c;这些内核对象可以是静态分配的静态对象&#xff0c;也可以是从系统内存堆中分配的动态对象。 RT-Thread内核对象包括&#xff1a;线程&…

使用VisualStutio2022开发第一个C++程序

使用VisualStudio2022创建C项目 第一步&#xff1a;新建C的控制台应用 第二步&#xff1a;填写项目名称和代码存放位置&#xff0c;代码的存放目录不要有中文名 第三步:点击创建&#xff0c;VisualStudio会自动开始帮我们创建项目 第四步&#xff1a;项目创建好以后&…

2023,测试开发人的年终总结

根据TesterHome社区发帖整理。从该年终总结可以看出当前测试行业&#xff0c;哪怕是测试开发也是竞争很厉害。作为测试同仁&#xff0c;不但要掌握功能测试、接口测试、性能测试&#xff0c;掌握各种工具使用&#xff0c;还得懂开发&#xff0c;懂Java/Python&#xff0c;懂VUE…

洛谷——P3884 [JLOI2009] 二叉树问题(最近公共祖先,LCA)c++

文章目录 一、题目[JLOI2009] 二叉树问题题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 提示基本思路&#xff1a; 一、题目 [JLOI2009] 二叉树问题 题目描述 如下图所示的一棵二叉树的深度、宽度及结点间距离分别为&#xff1a; 深度&#xff1a; 4 4 4宽度&…

【AI提示词故事】《启示之星:寻找神殿的探险之旅》

故事叙述 在未来的某一天&#xff0c;人类发现了一个神秘的星球&#xff0c;被称为“启示之星”。传说在这颗星球上&#xff0c;有一座可以实现人类最大愿望的神殿。 启示之星 一支探险队被派往这颗星球&#xff0c;他们的目标是寻找神殿并实现自己的最大愿望。 在星球上&…

关于 curl 常用命令的使用整理【不定期更新】

目录 1. HTTP 请求2. 文件操作2.1 文件下载2.2 文件上传2.3 FTP 操作 3. 代理和网络设置4. 身份验证5. 调试和信息显示6. more and more curl 是一个用于在命令行下进行数据传输的工具&#xff0c;支持多种协议&#xff0c;包括 HTTP、HTTPS、FTP、FTPS、SCP、SFTP 等。它通常用…

结合ChatGPT和MINDSHOW自动生成PPT

总结/朱季谦 一、首先&#xff0c;通过chatGPT说明你的需求&#xff0c;学会提问是Ai时代最关键的一步。你需要提供一些关键信息&#xff0c;如果没有关键信息&#xff0c;就按照大纲方式让它设计&#xff0c;例如&#xff0c;我让它帮我写一份《2023年年中述职报告》的模版—…

项目管理进阶之序言

背景 可能任何一个程序猿/媛都有一个梦想&#xff0c;立志成为一个技术Leader&#xff0c;带领一个Team&#xff0c;完成一个组织中重要的Project。 有些人天赋异禀&#xff0c;光彩夺目&#xff0c;从小已形成的某些特质&#xff0c;足以让他/她胜任这个领域&#xff0c;我们…

第11章 GUI Page428 步骤七 设置圆,矩形,文字的前景色

运行效果&#xff1a; 关键代码&#xff1a; 分别设置圆&#xff0c;矩形&#xff0c;和文字的画笔颜色&#xff0c;其中文字的设置方法稍有不同 圆&#xff1a; 矩形&#xff1a; 文字&#xff1a;
最新文章