【算法训练-链表】合并两个有序链表、合并K个有序链表

news2024/11/22 14:03:45

废话不多说,喊一句号子鼓励自己:程序员永不失业,程序员走向架构!首先,链表对应的数据结构在这篇Blog中:【基本数据结构 一】线性数据结构:链表,基于对基础知识的理解来进行题目解答。本篇Blog的主题是合并有序链表,近半年考察还是比较多的
在这里插入图片描述

合并两个有序链表

先来个基础版,合并两个已排序的链表

题干

在这里插入图片描述

输入:
{1,3,5},{2,4,6}

返回值:
{1,2,3,4,5,6}
输入:
{},{}

返回值:
{}
输入:
{-1,2,4},{1,3,4}

返回值:
{-1,1,2,3,4,4}

解题思路

整体目标就是在两个链表中选节点来构建新链表

  1. 设置result为哨兵结点,放置于新链表之前,最后返回的就是dummy.next
  2. 设置cur为当前节点,从dummy开始,当两个链表都非空时进入循环,令新链表的下一个节点cur.next为val更小的节点,相应的链表节点后移一位,每次循环记得cur也要后移一位
  3. 如果循环结束后还有链表非空,cur指向非空链表,返回dummy.next

如下图所示:
在这里插入图片描述

解题代码

Java实现

import java.util.*;

/*
 * public class ListNode {
 *   int val;
 *   ListNode next = null;
 *   public ListNode(int val) {
 *     this.val = val;
 *   }
 * }
 */

public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     *
     * @param pHead1 ListNode类
     * @param pHead2 ListNode类
     * @return ListNode类
     */
    public ListNode Merge (ListNode pHead1, ListNode pHead2) {
        // 1 如果其中一个为空列表,直接返回另一个
        if (pHead1 == null && pHead2 == null) {
            return null;
        }
        if (pHead1 == null) {
            return pHead2;
        }
        if (pHead2 == null) {
            return pHead1;
        }

        // 2 定义哨兵节点用来返回最终链表,cur用来检索两个链表的较小值节点
        ListNode dummy = new ListNode(-1);
        ListNode cur = dummy;
        while (pHead1 != null && pHead2 != null) {
            if (pHead1.val <= pHead2.val) {
                cur.next = pHead1;
                pHead1 = pHead1.next;
            } else {
                cur.next = pHead2;
                pHead2 = pHead2.next;
            }
            cur = cur.next;
        }

         // 3 一个链表空后,直接将另一个链表剩余部分拼接
        if (pHead1 == null) {
            cur.next = pHead2;
        }
        if (pHead2 == null) {
            cur.next = pHead1;
        }
        return dummy.next;
    }
}

时间复杂度O(N+M):M N分别表示pHead1, pHead2的长度; 空间复杂度O(1):常数级空间

合并K个有序链表

OK,难度升级,两个变成K个有序链表进行合并

题干

在这里插入图片描述

输入:
[{1,2,3},{4,5,6,7}]

返回值:
{1,2,3,4,5,6,7}
输入:
[{1,2},{1,4,5},{6}]

返回值:
{1,1,2,4,5,6}

解题思路

归并排序是什么?简单来说就是将一个数组每次划分成等长的两部分,对两部分进行排序即是子问题。对子问题继续划分,直到子问题只有1个元素。还原的时候呢,将每个子问题和它相邻的另一个子问题利用上述双指针的方式,1个与1个合并成2个,2个与2个合并成4个,因为这每个单独的子问题合并好的都是有序的,直到合并成原本长度的数组。

对于这k个链表,就相当于上述合并阶段的k个子问题,需要划分为链表数量更少的子问题,直到每一组合并时是两两合并,然后继续往上合并,这个过程基于递归:

  • 终止条件: 划分的时候直到左右区间相等或左边大于右边。
  • 返回值: 每级返回已经合并好的子问题链表。
  • 本级任务: 对半划分,将划分后的子问题合并成新的链表。

具体做法:

  1. 步骤一:从链表数组的首和尾开始,每次划分从中间开始划分,划分成两半,得到左边n/2个链表和右边n/2个链表
  2. 步骤二:继续不断递归划分,直到每部分链表数为1.
  3. 步骤三:将划分好的相邻两部分链表,按照两个有序链表合并的方式合并,合并好的两部分继续往上合并,直到最终合并成一个链表。

如下图所示,先进行合并链表划分
在这里插入图片描述
划分完子问题,子问题处理完返回到上一层级
在这里插入图片描述

解题代码

Java实现代码

import java.util.*;

/*
 * public class ListNode {
 *   int val;
 *   ListNode next = null;
 *   public ListNode(int val) {
 *     this.val = val;
 *   }
 * }
 */

public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     *
     * @param lists ListNode类ArrayList
     * @return ListNode类
     */
    public ListNode mergeKLists (ArrayList<ListNode> lists) {
        return divideMerge(lists, 0, lists.size() - 1);
    }

    private ListNode divideMerge(ArrayList<ListNode> lists, int left, int right) {
        if (left > right) {
            return null;
        }
        // 终止条件,拿到了单独的链表
        if (left == right) {
            return lists.get(left);
        }
        int mid = (left + right) / 2;
        // 对两个相邻链表进行合并
        return merge(divideMerge(lists, left, mid), divideMerge(lists, mid + 1, right));
    }

    private ListNode merge(ListNode pHead1, ListNode pHead2) {
        // 1 链表非空判断
        if (pHead1 == null && pHead2 == null) {
            return null;
        }
        if (pHead1 == null) {
            return pHead2;
        }
        if (pHead2 == null) {
            return pHead1;
        }

        // 2 定义哨兵节点和最终返回链表
        ListNode dummy = new ListNode(-1);
        ListNode cur = dummy;
        while (pHead1 != null && pHead2 != null) {
            if (pHead1.val <= pHead2.val) {
                cur.next = pHead1;
                pHead1 = pHead1.next;
            } else {
                cur.next = pHead2;
                pHead2 = pHead2.next;
            }
            cur = cur.next;
        }

        // 3 如果一个链表空了,直接挂载另一个链表剩余节点
        if (pHead1 == null) {
            cur.next = pHead2;
        }
        if (pHead2 == null) {
            cur.next = pHead1;
        }

        return dummy.next;
    }
}

时间复杂度:O(nlogk),其中n为所有链表的总节点数,分治为二叉树型递归,最坏情况下二叉树每层合并都是O(n)个节点,而分治次数为:O(logk),所以总的时间复杂度就是O(nlogk)

空间复杂度:最坏情况下合并O(logk),所以递归栈的深度为O(logk)

拓展:递归和分治的区别

递归(Recursion)和分治(Divide and Conquer)是两种解决问题的方法,它们有一些相似之处,但也存在一些关键的区别。

递归(Recursion)
递归是一种问题解决方法,其中函数在其执行过程中调用自身来解决更小规模的子问题,直到达到基本情况,然后逐层返回结果以构建原始问题的解。递归的核心思想是将一个大问题划分为与原问题结构相同但规模更小的子问题,然后通过逐步解决这些子问题来解决原始问题。

分治(Divide and Conquer)
分治是一种将问题分解为多个相互独立的子问题,然后解决这些子问题并将它们的解合并以得到原始问题解的方法。分治的核心思想是将一个大问题划分为多个独立的子问题,这些子问题可以并行地解决,然后将它们的解合并起来以得到原问题的解。

关键区别:

  1. 目标

    • 递归的目标是通过将问题分解为与原问题结构相同但规模更小的子问题,然后逐层解决这些子问题来得到原问题的解。
    • 分治的目标是将问题分解为多个相互独立的子问题,然后并行地解决这些子问题,并将它们的解合并以得到原问题的解。
  2. 子问题之间的关系

    • 递归中,子问题之间的关系通常是相同结构的,但规模不同。
    • 分治中,子问题通常是相互独立的,没有交叉。
  3. 解决方式

    • 递归解决问题时,通常需要考虑如何将问题分解为更小的子问题,然后将这些子问题的解组合起来。
    • 分治解决问题时,通常需要考虑如何将问题分解为独立的子问题,然后并行地解决这些子问题,最后将它们的解合并起来。
  4. 合并过程

    • 递归中,解决子问题的过程是逐层递归地调用函数,然后逐层返回结果。
    • 分治中,解决子问题的过程可以并行地进行,然后将子问题的解合并。

总的来说,递归和分治都是将复杂问题分解为更小的部分来解决的方法,但它们的关注点和执行方式有所不同。在实际应用中,有时候递归和分治可以结合使用,例如在分治算法的子问题解决过程中使用递归。

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

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

相关文章

(笔记二)利用opencv调用鼠标事件在图像上绘制图形

目录 &#xff08;1&#xff09;查看cv2所支持的鼠标事件&#xff08;2&#xff09;通过鼠标事件在图像上做标记&#xff08;3&#xff09;高级操作&#xff1a;通过移动鼠标在图像绘制图形、曲线 该功能主要创建一个鼠标事件发生时执行的回调函数。鼠标事件可以是任何与鼠标有…

配置Flink

配置flink_1.17.0 1.Flink集群搭建1.1解压安装包1.2修改集群配置1.3分发安装目录1.4启动集群、访问Web UI 2.Standalone运行模式3.YARN运行模式4.K8S运行模式 1.Flink集群搭建 1.1解压安装包 链接: 下载Flink安装包 解压文件 [gpbhadoop102 software]$ tar -zxvf flink-1.1…

前端需要理解的性能优化知识

优化的目的是展示更快、交互响应快、页面无卡顿情况。 1 性能指标 2 分析方法 使用 ChromeDevTool 作为性能分析工具来观察页面性能情况。其中Network观察网络资源加载耗时及顺序&#xff0c;Performace观察页面渲染表现及JS执行情况&#xff0c;Lighthouse对网站进行整体评分…

Linux驱动-I2C子系统基本分析

​第一&#xff1a;Linux中I2C驱动框架分析 I2C核心&#xff08;i2c_core&#xff09; I2C核心维护了i2c_bus结构体&#xff0c;提供了I2C总线驱动和设备驱动的注册、注销方法&#xff0c;维护了I2C总线的驱动、设备链表&#xff0c;实现了设备、驱动的匹配探测。此部分代码由…

CNN 01(CNN简介)

一、卷积神经网络的发展 convolutional neural network 在计算机视觉领域&#xff0c;通常要做的就是指用机器程序替代人眼对目标图像进行识别等。那么神经网络也好还是卷积神经网络其实都是上个世纪就有的算法&#xff0c;只是近些年来电脑的计算能力已非当年的那种计算水平…

sql语句中的ddl和dml

操作数据库&#xff1a;CRUD C&#xff08;create&#xff09; 创建 *数据库创建出来默认字符集为utf8 如果要更改字符集就 Create database 名称 character set gbk&#xff08;字符集&#xff09; *创建数据库&#xff1a;create database 名称 *先检查是否有该数据库在…

Python第三方库纵览

Python第三方库纵览 知识点 更广泛的Python计算生态&#xff0c;只要求了解第三方库的名称&#xff0c;不限于以下领域: 网络爬虫、数据分析、文本处理、数据可视化、用户图形界面、机器学习、Web开发、游戏开发等 知识导图 1、网络爬虫方向 网络爬虫是自动进行HTTP访问并捕…

【JAVA】实现API 接口参数签名

使用sa-tokenSpringBoot拦截器实现API 接口参数签名 在涉及跨系统接口调用时&#xff0c;我们容易碰到以下安全问题&#xff1a; 1.请求身份被伪造。 2.请求参数被篡改。 3.请求被抓包&#xff0c;然后重放攻击。 1.引入 sa-token sa-token官方文档:https://sa-token.cc/doc.ht…

HCIP-HCS华为私有云

1、概述 HCS&#xff08;HuaweiCoudStack&#xff09;华为私有云&#xff1a;6.3 之前叫FusionSphere OpenStack&#xff0c;6.3.1 版本开始叫FusionCloud&#xff0c;6.5.1 版本开始叫HuaweiCloud Stack (HCS)华为私有云软件。 开源openstack&#xff0c;发放云主机的流程&am…

第五章 树与二叉树 一、树的定义与考点

一、定义 1.树是由n (n > 0) 个节点组成的有限集合。 2.当n0时&#xff0c;称为空树。 3.在非空树中&#xff0c;有且仅有一个节点没有前驱&#xff0c;其他节点都有且仅有一个前驱&#xff0c;称为根节点。 4.每个节点有零个或多个子节点&#xff0c;而每个子节点又有零…

多态/虚函数/虚函数表

OVERVIEW 多态/虚函数/虚函数表1.虚函数引入后类发生的变化&#xff1f;2.虚函数表的生成时机和生成原因&#xff1f;3.虚函数表指针赋值的时机&#xff1f;4.类对象在内存中的布局&#xff1f;5.虚函数的工作原理和多态性的体现&#xff1f;6.其他问题 多态/虚函数/虚函数表 n…

Android JNI系列详解之生成指定CPU的库文件

一、前提 这次主要了解Android的cpu架构类型&#xff0c;以及在使用CMake工具的时候&#xff0c;如何指定生成哪种类型的库文件。 如上图所示&#xff0c;是我们之前使用CMake工具默认生成的四种cpu架构的动态库文件&#xff1a;arm64-v8a、armeabi-v7a、x86、x86_64&#xff0…

昇腾Ascend+C编程入门教程(纯干货)

2023年5月6日&#xff0c;在昇腾AI开发者峰会上&#xff0c;华为正式发布了面向算子开发场景的昇腾Ascend C编程语言。Ascend C原生支持C/C编程规范&#xff0c;通过多层接口抽象、并行编程范式、孪生调试等技术&#xff0c;极大提高了算子的开发效率&#xff0c;帮助AI开发者低…

go学习之流程控制语句

文章目录 流程控制语句1.顺序控制2.分支控制2.1单分支2.2双分支单分支和双分支的四个题目switch分支结构 3.循环控制for循环控制while 和do...while的实现 4.跳转控制语句breakcontinuegotoreturngotoreturn 流程控制语句 介绍&#xff1a;在程序中&#xff0c;程序运行的流程…

星际争霸之小霸王之小蜜蜂(七)--消失的子弹

目录 前言 一、删除子弹 二、限制子弹数量 三、继续重构代码 总结 前言 昨天我们已经让子弹飞了起来&#xff0c;但是会面临一个和之前小蜜蜂一样的问题&#xff0c;小蜜蜂的行动应该限制在窗口内&#xff0c;那么子弹也是有相同之处&#xff0c;也需要限制一个移动范围&…

智慧监狱整体解决方案PPT

导读&#xff1a;原文《智慧监狱整体解决方案PPT》&#xff08;获取来源见文尾&#xff09;&#xff0c;本文精选其中精华及架构部分&#xff0c;逻辑清晰、内容完整&#xff0c;为快速形成售前方案提供参考。 喜欢文章&#xff0c;您可以点赞评论转发本文&#xff0c;了解更多…

全球互联网裁员下测试人员何去何从?

时间好像突然加快了步伐瞬间觉得匆匆&#xff0c;转眼已经23年&#xff0c;从20年到23年。回想起来恍恍惚惚&#xff0c;疫情中经历的种种就好像没有发生过一样&#xff0c;很多的魑魅魍魉荒唐可笑真实又虚幻&#xff0c;时光向前人生向后&#xff0c;那些魔幻的人和事也慢慢消…

可解释性的相关介绍

一、可解释性的元定义&#xff08;Meta-definitions of Interpretability&#xff09; The extent to which an individual can comprehend the cause of a model’s outcome. [1]The degree to which a human can consistently predict a model’s outcome. [2] 可解释性&am…

Flutter 项目结构文件

1、Flutter项目的文件结构 先helloworld项目&#xff0c;看看它都包含哪些组成部分。首先&#xff0c;来看一下项目的文件结构&#xff0c;如下图所示。 2、介绍上图的内容。 -litb/main.dart文件&#xff1a;整个应用的入口文件&#xff0c;其中的main函数是整个Flutter应…