LeetCode 题目 88:双指针\直接\递归\插入排序\归并排序 实现合并两个有序数组

在线体验各类最新模型,更有模型 免费Token 额度领取!
立即体验
简介: LeetCode 题目 88:双指针\直接\递归\插入排序\归并排序 实现合并两个有序数组

作者介绍:10年大厂数据\经营分析经验,现任大厂数据部门负责人。

会一些的技术:数据分析、算法、SQL、大数据相关、python

欢迎加入社区:码上找工作

作者专栏每日更新:

LeetCode解锁1000题: 打怪升级之旅

python数据分析可视化:企业实战案例

python源码解读

程序员必备的数学知识与应用

备注说明:方便大家阅读,统一使用python,带必要注释,公众号 数据分析螺丝钉 一起打怪升级

题目描述

给定两个有序整数数组 nums1nums2,将 nums2 合并到 nums1 中,使得 nums1 成为一个有序数组。

初始化 nums1nums2 的元素数量分别为 mn。可以假设 nums1 有足够的空间(空间大小为 m + n)来保存 nums2 中的元素。

输入格式
  • nums1:第一个有序数组,长度为 m + n,后 n 个元素为 0,用于存放合并后的数组。
  • nums2:第二个有序数组,长度为 n
  • mnums1 中初始化的元素数量。
  • nnums2 中的元素数量。
输出格式
  • 直接在 nums1 上修改,无需返回值。

示例

示例 1
输入: nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出: [1,2,2,3,5,6]

方法一:直接合并后排序

解题步骤
  1. 合并数组:将 nums2 直接合并到 nums1 的末尾。
  2. 排序:对合并后的 nums1 进行排序。
完整的规范代码
def merge(nums1, m, nums2, n):
    """
    合并后直接排序
    :param nums1: List[int], 第一个有序数组
    :param m: int, nums1 的元素数量
    :param nums2: List[int], 第二个有序数组
    :param n: int, nums2 的元素数量
    """
    nums1[m:m+n] = nums2
    nums1.sort()
# 示例调用
nums1 = [1,2,3,0,0,0]
m = 3
nums2 = [2,5,6]
n = 3
merge(nums1, m, nums2, n)
print(nums1)  # 输出: [1,2,2,3,5,6]
算法分析
  • 时间复杂度:(O((m+n) log (m+n))),排序操作的复杂度。
  • 空间复杂度:(O(1)),直接在输入数组上操作,不需要额外空间。

方法二:双指针从后向前

解题步骤
  1. 使用三个指针:分别指向 nums1nums2 的末尾,以及 nums1 合并后的末尾。
  2. 逆序合并:从后向前比较 nums1nums2,将较大的值放在合并后数组的末尾。
完整的规范代码
def merge(nums1, m, nums2, n):
    """
    双指针从后向前合并数组
    :param nums1: List[int], 第一个有序数组
    :param m: int, nums1 的元素数量
    :param nums2: List[int], 第二个有序数组
    :param n: int, nums2 的元素数量
    """
    i, j, k = m - 1, n - 1, m + n - 1
    while j >= 0:
        if i >= 0 and nums1[i] > nums2[j]:
            nums1[k] = nums1[i]
            i -= 1
        else:
            nums1[k] = nums2[j]
            j -= 1
        k -= 1
# 示例调用
nums1 = [1,2,3,0,0,0]
m = 3
nums2 = [2,5,6]
n = 3
merge(nums1, m, nums2, n)
print(nums1)  # 输出: [1,2,2,3,5,6]
算法分析
  • 时间复杂度:(O(m+n)),每个元素最多处理一次。
  • 空间复杂度:(O(1)),直接在输入数组上操作。

方法三:递归合并

这是一个理论上的方法,不适用于这个问题,因为它不满足题目的空间要求,而且 Python 的递归也可能导致栈溢出。但为了完整性,这里给出思路:

解题步骤
  1. 递归基:如果 nums2 为空,递归结束。
  2. 递归合并:比较 nums1nums2 的最后一个元素,选择较大者放在最终位置,递归处理剩余数组。

由于 Python 默认的递归深度限制,以及递归方法通常需要额外的空间用于调用栈,这种方法并不适用于大数组或本题的要求。实际应用中应该避免使用递归方法处理这类问题。

方法四:插入排序思想

解题步骤
  1. 初始化:将 nums2 中的元素插入到 nums1 中合适的位置,确保 nums1 保持有序。
  2. 移动元素:为了插入元素,需要移动 nums1 中的元素以保持数组的有序性。
完整的规范代码
def merge(nums1, m, nums2, n):
    """
    使用插入排序的思想合并两个数组
    :param nums1: List[int], 第一个有序数组
    :param m: int, nums1 的元素数量
    :param nums2: List[int], 第二个有序数组
    :param n: int, nums2 的元素数量
    """
    for num in nums2:
        i = 0
        while i < m and nums1[i] < num:
            i += 1
        nums1[i+1:m+1] = nums1[i:m]  # 移动元素,为新元素腾出空间
        nums1[i] = num
        m += 1
# 示例调用
nums1 = [1,2,3,0,0,0]
m = 3
nums2 = [2,5,6]
n = 3
merge(nums1, m, nums2, n)
print(nums1)  # 输出: [1,2,2,3,5,6]
算法分析
  • 时间复杂度:(O(m \times n)),每次插入操作平均需要移动 m/2 个元素,插入 n 次。
  • 空间复杂度:(O(1)),直接在输入数组上操作,不需要额外空间。

方法五:归并排序的合并阶段

解题步骤
  1. 使用额外数组:创建一个临时数组来存放合并的结果。
  2. 归并操作:像归并排序的合并阶段那样,从前向后比较两个数组的元素,将较小的元素先添加到临时数组中。
完整的规范代码
def merge(nums1, m, nums2, n):
    """
    使用归并排序的合并思想来合并两个有序数组
    :param nums1: List[int], 第一个有序数组
    :param m: int, nums1 的元素数量
    :param nums2: List[int], 第二个有序数组
    :param n: int, nums2 的元素数量
    """
    temp = []
    i, j = 0, 0
    while i < m and j < n:
        if nums1[i] < nums2[j]:
            temp.append(nums1[i])
            i += 1
        else:
            temp.append(nums2[j])
            j += 1
    # 处理剩余的元素
    while i < m:
        temp.append(nums1[i])
        i += 1
    while j < n:
        temp.append(nums2[j])
        j += 1
    # 将合并后的数组复制回 nums1
    nums1[:m+n] = temp
# 示例调用
nums1 = [1,2,3,0,0,0]
m = 3
nums2 = [2,5,6]
n = 3
merge(nums1, m, nums2, n)
print(nums1)  # 输出: [1,2,2,3,5,6]
算法分析
  • 时间复杂度:(O(m+n)),每个元素只处理一次。
  • 空间复杂度:(O(m+n)),需要一个临时数组来存储合并后的结果。

不同算法的优劣势对比

特征 方法一:直接合并后排序 方法二:双指针从后向前 方法三:递归合并 方法四:插入排序思想 方法五:归并排序合并
时间复杂度 (O((m+n) \log (m+n))) (O(m+n)) (O(m \times n)) (O(m \times n)) (O(m+n))
空间复杂度 (O(1)) (O(1)) (O(m+n)) (O(1)) (O(m+n))
优势 简单易实现 最优时间复杂度,原地合并 保持原始方法逻辑,不实用 避免了排序,逐个插入 类似外部排序,清晰逻辑
劣势 时间较长,非最优解 需要注意边界条件 需要额外的空间,非原地操作 时间复杂度较高,不适用于大数据 需要额外空间,非原地操作

应用示例

这些方法在实际应用中非常有用,特别是在处理需要合并多个数据流或数据库查询结果时。例如,在数据库管理、日志文件处理和数据清洗等场景中,经常需要将多个有序数据流合并成一个有序的数据结构。这些技术能有效地优化这些操作。


欢迎关注微信公众号 数据分析螺丝钉

相关文章
|
算法 容器
【算法】——双指针算法合集(力扣)
移动零,复写零,快乐数,盛最多水的容器,有效三角形的个数,和为s的两个数(查找总价格为目标值的两个商品 ),三数之和,四数之和
|
程序员 C语言
【C语言】LeetCode(力扣)上经典题目
【C语言】LeetCode(力扣)上经典题目
367 1
|
算法 Java
LeetCode经典算法题:矩阵中省份数量经典题目+三角形最大周长java多种解法详解
LeetCode经典算法题:矩阵中省份数量经典题目+三角形最大周长java多种解法详解
258 6
|
存储 编译器 C语言
【C语言】指针练习题目
【C语言】指针练习题目
474 2
LeetCode第12题目整数转罗马数字
该文章介绍了 LeetCode 第 12 题整数转罗马数字的解法,通过使用 TreeMap 按照整数从大到小排序,先使用大的罗马数字表示整数,再用小的,核心是先表示完大的罗马数字,想通此点该题较简单。
LeetCode第12题目整数转罗马数字
|
SQL Oracle 关系型数据库
CASE WHEN 语句的语法及示例,LeetCode 题目 “确认率” 练习
本文介绍了SQL中CASE语句的两种形式和语法,并通过LeetCode题目“确认率”的SQL查询示例展示了CASE语句在实际问题中的应用,解释了如何使用CASE语句计算特定条件的比率。
LeetCode第13题目罗马数字转整数
该文章介绍了 LeetCode 第 13 题罗马数字转整数的解法,通过从大到小解析罗马数字,根据罗马数字的特点,按照从大到小的顺序匹配罗马数字和整数的关系,从而解决该问题,同时强调要注意观察题目考查的知识点特征。
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
2082 13
|
存储 人工智能 C语言
C语言程序设计核心详解 第八章 指针超详细讲解_指针变量_二维数组指针_指向字符串指针
本文详细讲解了C语言中的指针,包括指针变量的定义与引用、指向数组及字符串的指针变量等。首先介绍了指针变量的基本概念和定义格式,随后通过多个示例展示了如何使用指针变量来操作普通变量、数组和字符串。文章还深入探讨了指向函数的指针变量以及指针数组的概念,并解释了空指针的意义和使用场景。通过丰富的代码示例和图形化展示,帮助读者更好地理解和掌握C语言中的指针知识。
940 4