算法题回顾:双指针链表系列集锦

1,合并两个有序链表

 思路

  • 创建一个指向空的新链表,用来存储合并后的链表,p指针指向该链表。
  • 创建双指针,分辨指向两个链表,用p1, p2表示
  • while循环,依次判断两个指针指向数据的大小,将最小值赋值在p指针的当前值。将最小值的指针指向下一个节点,将p指针指向下一个节点。当p1或p2指向空指针时候退出while循环
  • 判断如果p1指针不为空,将p->next指向p1->next
  • 判断如果p2指针不为空,将p->nex指向p2->next

代码

ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
    // 虚拟头结点
    ListNode* dummy = new ListNode(-1), *p = dummy;
    ListNode* p1 = l1, *p2 = l2;
    
    while (p1 != NULL && p2 != NULL) {
        // 比较 p1 和 p2 两个指针
        // 将值较小的的节点接到 p 指针
        if (p1->val > p2->val) {
            p->next = p2;
            p2 = p2->next;
        } else {
            p->next = p1;
            p1 = p1->next;
        }
        // p 指针不断前进
        p = p->next;
    }
    
    if (p1 != NULL) {
        p->next = p1;
    }
    
    if (p2 != NULL) {
        p->next = p2;
    }
    
    return dummy->next;
}

2, 单链表的分解

给定一个链表的头节点head,和一个特定值 x,对链表进行分隔,使所有小于x的节点都放在x节点之前,大于等于x的放在x节点之后。保留两个分区中每个节点的相对位置。

思路

  • 创建两个链表,分别存放小于x的数,和大于等于x的数的节点,指针为p1,p2
  • 合并p1和p2的节点即为所求。

注意代码中每个while循环都先把p->next赋给tmp新链表,然后把p->next节点置为nullptr,是因为每次都是把p指针直接赋值给了p1->next,或者p2->next。为了防止p1,p2的链表指向混乱的p节点,所以需要把p->next节点置为nullptr

 代码


ListNode* partition(ListNode* head, int x) {
    // 存放小于 x 的链表的虚拟头结点
    ListNode* dummy1 = new ListNode(-1);
    // 存放大于等于 x 的链表的虚拟头结点
    ListNode* dummy2 = new ListNode(-1);
    // p1, p2 指针负责生成结果链表
    ListNode* p1 = dummy1, * p2 = dummy2;
    // p 负责遍历原链表,类似合并两个有序链表的逻辑
    // 这里是将一个链表分解成两个链表
    ListNode* p = head;
    while (p != nullptr) {
        if (p->val >= x) {
            p2->next = p;
            p2 = p2->next;
        } else {
            p1->next = p;
            p1 = p1->next;
        }
        // 断开原链表中的每个节点的 next 指针
        ListNode* temp = p->next;
        p->next = nullptr;
        p = temp;
    }
    // 连接两个链表
    p1->next = dummy2->next;

    return dummy1->next;
}

3, 合并k个有序链表

给一个链表数组,每个链表都是升序排列。请将所有链表合并为一个升序链表,并返回。

 思路

  • 创建一个最小堆,把每个链表的头节点存入堆
  • while循环判断,每次从堆中取出一个当前最小值,把节点放在结果链表中。然后如果取出最小值的链表下一个节点不为空,就将其指向下一个节点,再放入到最小堆中。当最小堆中没有链表时候,退出while循环。

时间复杂度: O(Nlogk), k是链表的条数,N是这些链表的节点总数。

代码

ListNode* mergeKLists(vector<ListNode*>& lists) {
    if (lists.empty()) return nullptr;
    // 虚拟头结点
    ListNode* dummy = new ListNode(-1);
    ListNode* p = dummy;
    // 优先级队列,最小堆
    priority_queue<ListNode*, vector<ListNode*>, function<bool(ListNode*, ListNode*)>> pq(
        [] (ListNode* a, ListNode* b) { return a->val > b->val; });
    // 将 k 个链表的头结点加入最小堆
    for (auto head : lists) {
        if (head != nullptr) {
            pq.push(head);
        }
    }

    while (!pq.empty()) {
        // 获取最小节点,接到结果链表中
        ListNode* node = pq.top();
        pq.pop();
        p->next = node;
        if (node->next != nullptr) {
            pq.push(node->next);
        }
        // p 指针不断前进
        p = p->next;
    }
    return dummy->next;
}

4, 求单链表的倒数第k个节点

基本版本:遍历两遍链表,第一遍 知道链表的长度n,第二遍,倒数第k个节点,就是正数第n-k+1个节点,输出第n-k+1个节点。

双指针版本:

  • 常见第一个指针p1,并走到k个节点,
  • 然后创建第2个指针p2, p2指向头指针
  • p1和p2指针同步向next前进,当p1指针指向原始链表p的末尾nullptr时候,p2恰好走到第n-k+1的位置。

 代码

// 返回链表的倒数第 k 个节点
ListNode* findFromEnd(ListNode* head, int k) {
    ListNode* p1 = head;
    // p1 先走 k 步
    for (int i = 0; i < k; i++) {
        p1 = p1 -> next;
    }
    ListNode* p2 = head;
    // p1 和 p2 同时走 n - k 步
    while (p1 != nullptr) {
        p2 = p2 -> next;
        p1 = p1 -> next;
    }
    // p2 现在指向第 n - k + 1 个节点,即倒数第 k 个节点
    return p2;
}

拓展,当需要删除链表的倒数第N个节点时候,可以直接用上面代码先找到倒数第n+1个节点x,然后把x->next指针指向x->next->next即可。

5, 求单链表的中点

思路
同理,创建两个指针slow, fast分别指向链表头节点head。

slow每次前进一步,fast每次前进2步,当fast走到head链表的末尾时候,slow就在链表的中点位置。

注意:如果链表中点有两个,则会返回靠后面的那个节点。

代码

ListNode* middleNode(ListNode* head) {
    // 快慢指针初始化指向 head
    ListNode* slow = head;
    ListNode* fast = head;
    // 快指针走到末尾时停止
    while (fast != nullptr && fast->next != nullptr) {
        // 慢指针走一步,快指针走两步
        slow = slow->next;
        fast = fast->next->next;
    }
    // 慢指针指向中点
    return slow;
}

6,判断链表是否包含环,以及环的起点

思路

还是通过快慢指针实现。slow指针前进一步,fast前进两步。

如果fast指针最终遇到空指针,说明链表中没有环;如果fast最终和slow相遇,那就是fast超过了slow一圈,说明链表中有环。

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

判断环的起点:只需要在fast和slow相遇时候,将slow节点重新指向头节点,然后slow和fast指针开始同步前进,当再次相遇时候,即为环的起始节点。

如下示意图,首次相遇,slow走了k步, fast走了2k步,环的长度是k,  假设相遇节点离环的起点距离是n, 则从头节点到slow位置是k, 从头节点到环起点begin是k-n。 环的长度减去环起点到slow的距离也是-n。所以从首次相遇的地方,重新让slow指向头节点,fast和slow同步前进,当再次相遇时候,即为环的起始节点。

代码

ListNode* detectCycle(ListNode* head) {
    ListNode* fast = head;
    ListNode* slow = head;
    while (fast != nullptr && fast->next != nullptr) {
        fast = fast->next->next;
        slow = slow->next;
        if (fast == slow) break;
    }
    // 上面的代码类似 hasCycle 函数
    if (fast == nullptr || fast->next == nullptr) {
        // fast 遇到空指针说明没有环
        return nullptr;
    }

    // 重新指向头结点
    slow = head;
    // 快慢指针同步前进,相交点就是环起点
    while (slow != fast) {
        fast = fast->next;
        slow = slow->next;
    }
    return slow;
}

7, 判断两个链表是否相交

如下图,给链表A为a1->a2->c1->c2, 链表B为 b1->b2->b3->c1->c2,如何判断A,B是否相交?

 思路

难点在于由于两条链表的长度可能不同,两条链表之间的节点无法对应。所以解决问题的关键是通过某种方式,让p1,p2分别指向A,B,让其能够同时到达节点c1。

我们可以让 p1 遍历完链表 A 之后开始遍历链表 B,让 p2 遍历完链表 B 之后开始遍历链表 A,这样相当于「逻辑上」两条链表接在了一起。

如果这样进行拼接,就可以让 p1 和 p2 同时进入公共部分,也就是同时到达相交节点 c1

 代码

// 求链表的交点
ListNode* getIntersectionNode(ListNode* headA, ListNode* headB) {
    // p1 指向 A 链表头结点,p2 指向 B 链表头结点
    ListNode *p1 = headA, *p2 = headB;
    while (p1 != p2) {
        // p1 走一步,如果走到 A 链表末尾,转到 B 链表
        if (p1 == nullptr) p1 = headB;
        else               p1 = p1->next;
        // p2 走一步,如果走到 B 链表末尾,转到 A 链表
        if (p2 == nullptr) p2 = headA;
        else               p2 = p2->next;
    }
    return p1; // 返回交点
}

另外一种思路是,首先得到A,B的链表长度,然后谁长,谁先走n步,用于使其同时到达最后一个节点。然后A,B同步前进,当相同时候退出程序,返回true,或者同时走到最后时候退出,返回false。

代码

ListNode* getIntersectionNode(ListNode* headA, ListNode* headB) {
    int lenA = 0, lenB = 0;
    // 计算两条链表的长度
    for (ListNode *p1 = headA; p1 != nullptr; p1 = p1->next) {
        lenA++;
    }
    for (ListNode *p2 = headB; p2 != nullptr; p2 = p2->next) {
        lenB++;
    }
    // 让 p1 和 p2 到达尾部的距离相同
    ListNode *p1 = headA, *p2 = headB;
    if (lenA > lenB) {
        // p1 先走过 lenA-lenB 步
        for (int i = 0; i < lenA - lenB; i++) {
            p1 = p1->next;
        }
    } else {
        // p2 先走过 lenB-lenA 步
        for (int i = 0; i < lenB - lenA; i++) {
            p2 = p2->next;
        }
    }
    // 看两个指针是否会相同,p1 == p2 时有两种情况:
    // 1、要么是两条链表不相交,他俩同时走到尾部空指针
    // 2、要么是两条链表相交,他俩走到两条链表的相交点
    while (p1 != p2) {
        p1 = p1->next;
        p2 = p2->next;  
    }
    return p1;
}

参考:双指针技巧秒杀七道链表题目 :: labuladong的算法小抄

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

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

相关文章

从零开始实现一个C++高性能服务器框架----日志模块

此项目是根据sylar框架实现&#xff0c;是从零开始重写sylar&#xff0c;也是对sylar丰富与完善 项目地址&#xff1a;https://gitee.com/lzhiqiang1999/server-framework 简介 项目介绍&#xff1a;实现了一个基于协程的服务器框架&#xff0c;支持多线程、多协程协同调度&am…

Vue3走马灯(Carousel)

Vue2走马灯&#xff08;Carousel&#xff09; 可自定义设置以下属性&#xff1a; 走马灯图片数组&#xff08;imageData&#xff09;&#xff0c;类型&#xff1a;Array<{title: string, link?: string, imgUrl: string}>&#xff0c;默认 [] 自动滑动轮播间隔&#…

3-ELK+Kafka+Filebeat 海量级日志收集 TB PB级别

ELKKafkaFilebeat 终极版 4、Kafka&#xff1a; 数据缓冲队列(消息队列)。同时提高了可扩展性。具有峰值处理能力&#xff0c;使用消息队列能够使关键组件顶住突发的访问压力&#xff0c;而不会因为突发的超负荷的请求而完全崩溃。是一个分布式、支持分区的&#xff08;partit…

模板匹配及应用

模板匹配及应用 1)模板匹配 模板匹配是一项在一幅图像中寻找与另一幅模板图像最匹配(相似)部分的技术。模板匹配不是基于直方图的, 而是通过在 输入图像上滑动图像块(模板)同时比对相似度, 来对模板和输入图像进行匹配的一种方法。 应用: ①目标查找定位 ②运动物体跟踪 ③…

SpringMvc中拦截器

文章目录 1.拦截器概述 2.拦截器类中的方法 1.先写一个前端页面 2.写后台代码 3.编写success.jsp页面 4.编写拦截器类&#xff0c;实现HandlerInterceptor接口 5.编写error.jsp页面 6.配置拦截器类 3.配置多个拦截器 3.1再写一个拦截器类 3.2 配置拦截器类 1.拦截器概述 Spring…

中国版ChatGPT即将来袭-国内版ChatGPT入口

必应chatGPT入口 目前并不存在“必应ChatGPT”这个概念。必应&#xff08;Bing&#xff09;是Microsoft公司推出的一款搜索引擎&#xff0c;而ChatGPT是OpenAI开发的自然语言处理技术&#xff0c;它们是两个不同的产品品牌。 不过&#xff0c;Microsoft也在自然语言处理领域里…

Leetcode字符串的排列

其实可以看成使用其中一个字符加上其他字符的连接&#xff0c;最后用set去重 class Solution:lru_cache(None)def permutation(self, s: str) -> List[str]:if not s: return []res set()for i in range(len(s)):for j in self.permutation(s[:i]s[i1:]):res.add(s[i]j)re…

Unity Animation -- 改进动画效果

使用曲线&#xff08;Curves&#xff09;改善动画 在上一篇笔记中&#xff08;Unity Animation -- Overview_亦枫Leonlew的博客-CSDN博客&#xff09;&#xff0c;我们制作了简单的小球弹跳的动画&#xff0c;但这个动画看起来很不自然&#xff0c;小球的弹跳看起来就像是不受…

Leetcode.559 N 叉树的最大深度

题目链接 Leetcode.559 N 叉树的最大深度 easy 题目描述 给定一个 N 叉树&#xff0c;找到其最大深度。 最大深度是指从根节点到最远叶子节点的最长路径上的节点总数。 N 叉树输入按层序遍历序列化表示&#xff0c;每组子节点由空值分隔&#xff08;请参见示例&#xff09;。…

Vector - CAPL - CRC算法介绍(续)

不常用CRC算法 目录 Crc_CalculateCRC8H2F 代码示例 Crc_CalculateCRC32P4 代码示例 Crc_CalculateCRC64 代码示例 Crc_CalculateCRC8H2F 功能&#xff1a;根据数据计算CRC8H2F的相应校验和。 data&#xff1a;待计算CRC8H2F校验和的数据 dataSize&#xff1a;待计算CRC…

Ansys Zemax | 如何使用 Zernike 凹陷表面对全反射系统进行建模

本文介绍如何使用Zernike标准下垂表面对全反射系统进行建模。全反射系统是一种特殊情况&#xff0c;其中Zernike凹陷表面可用于模拟给定场点的所有波长下的性能。使用Zernike凹陷表面代替Zernike相位&#xff0c;因为衍射功率与波长变化时的反射功率不同。一个相位波是任何波长…

linux 共享内存 shmget

专栏内容&#xff1a;linux下并发编程个人主页&#xff1a;我的主页座右铭&#xff1a;天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物&#xff0e;目录 前言 概述 原理机制 系统命令 接口说明 代码演示 结尾 前言 本专栏主要分享linu…

Day924.自动化测试 -系统重构实战

自动化测试 Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于自动化测试的内容。 自动化测试是一个很容易产生“争议”的话题&#xff0c;也经常会有一些很有意思的问题。 自动化测试不是应该由测试同学来编写吗&#xff0c;开发是不是没有必要学吧&#xff1f;之前…

【Linux】进程理解与学习-程序替换

环境&#xff1a;centos7.6&#xff0c;腾讯云服务器Linux文章都放在了专栏&#xff1a;【Linux】欢迎支持订阅 相关文章推荐&#xff1a; 【Linux】冯.诺依曼体系结构与操作系统 【Linux】进程理解与学习Ⅰ-进程概念 【Linux】进程理解与学习Ⅱ-进程状态 【Linux】进程理解与学…

小白的git入门教程(二)

泥闷嚎 今天接着来学习小白入门git的基本过程 今天要学习的是git里面的常见操作 状态查看 git status 所谓的状态查看就是你可以查看到工作区和暂存区的状态&#xff0c;在这里你可以看到你的工作文件的状态&#xff0c;比如是否已经提交等等 首先我们创建一个文本文件&…

FreeRTOS学习(一)

裸机与RTOS对比 裸机&#xff1a;又称为前后台系统&#xff0c;前台系统指的是中断服务函数&#xff0c;后台系统指的大循环&#xff0c;即应用程序。 实时性差&#xff1a;&#xff08;应用程序轮流执行&#xff09;delay&#xff1a;空等待&#xff0c;CPU不执行其它代码结…

【分享】太阳能电池性能测试指标,太阳能电池IV测试软件系统

在现代社会&#xff0c;随着能源需求的不断增加&#xff0c;太阳能电池的应用越来越广泛。太阳能电池是一种利用太阳光能量将化学能转换为电能的半导体材料&#xff0c;它可以将太阳光中的光能直接转换成电能&#xff0c;因此具有广泛的应用前景。本篇文章纳米软件小编为大家分…

JAVAWeb01-BS架构简述、HTML

1. B/S 软件开发架构简述 1.1 Java Web 技术体系图 1.2 B/S 软件开发架构简述 B/S架构 B/S框架&#xff0c;意思是前端(Browser 浏览器)和服务器端(Server)组成的系统的框架结构。B/S架构也可理解为web架构&#xff0c;包含前端、后端、数据库三大组成部分。示意图 &#xf…

学校的地下网站(学校的地下网站1080P高清)

这个问题本身就提得有问题&#xff0c;为什么这么说&#xff0c;这是因为YouTube本身就不是一个视频网站或者说YouTube不是一个传统的视频网站&#xff01;&#xff01;&#xff01; YouTube能够一家独大&#xff0c;可不仅仅是因为有了Google这个亲爹&#xff0c;还有一点&…

ROS实践12 自定义源文件并调用

文章目录运行环境&#xff1a;思路&#xff1a;原理&#xff1a;1.1 头文件编写1.2 编写源文件1.3 编写可执行文件1.4 &#x1f3ef;配置文件&#x1f3ef;1.5 编译运行运行环境&#xff1a; ubuntu20.04 noetic 宏基暗影骑士笔记本 思路&#xff1a; 上一期&#xff1a;类和…
最新文章