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

简介: 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))
优势 简单易实现 最优时间复杂度,原地合并 保持原始方法逻辑,不实用 避免了排序,逐个插入 类似外部排序,清晰逻辑
劣势 时间较长,非最优解 需要注意边界条件 需要额外的空间,非原地操作 时间复杂度较高,不适用于大数据 需要额外空间,非原地操作

应用示例

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


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

相关文章
leetCode(删除有序数组中的重复项)
如何在不使用额外空间的情况下,通过双指针法原地删除有序数组中的重复项。
130 2
|
10月前
|
Go 索引 Perl
【LeetCode 热题100】【二叉树构造题精讲:前序 + 中序建树 & 有序数组构造 BST】(详细解析)(Go语言版)
本文详细解析了二叉树构造的两类经典问题:通过前序与中序遍历重建二叉树(LeetCode 105),以及将有序数组转化为平衡二叉搜索树(BST,LeetCode 108)。文章从核心思路、递归解法到实现细节逐一拆解,强调通过索引控制子树范围以优化性能,并对比两题的不同构造逻辑。最后总结通用构造套路,提供进阶思考方向,帮助彻底掌握二叉树构造类题目。
648 9
|
算法 容器
【算法】——双指针算法合集(力扣)
移动零,复写零,快乐数,盛最多水的容器,有效三角形的个数,和为s的两个数(查找总价格为目标值的两个商品 ),三数之和,四数之和
|
程序员 C语言
【C语言】LeetCode(力扣)上经典题目
【C语言】LeetCode(力扣)上经典题目
289 1
LeetCode第80题删除有序数组中的重复项 II
文章介绍了LeetCode第80题"删除有序数组中的重复项 II"的解法,利用双指针技术在O(1)空间复杂度内原地删除重复元素,并总结了双指针技术在处理有序数组问题中的应用。
LeetCode第80题删除有序数组中的重复项 II
LeetCode第88题合并两个有序数组
文章分享了LeetCode第88题"合并两个有序数组"的解法,通过从后向前的合并策略避免了数组元素的前移,使用三个指针高效地完成了合并过程。
【LeetCode 48】108.将有序数组转换为二叉搜索树
【LeetCode 48】108.将有序数组转换为二叉搜索树
145 0
|
SQL Oracle 关系型数据库
CASE WHEN 语句的语法及示例,LeetCode 题目 “确认率” 练习
本文介绍了SQL中CASE语句的两种形式和语法,并通过LeetCode题目“确认率”的SQL查询示例展示了CASE语句在实际问题中的应用,解释了如何使用CASE语句计算特定条件的比率。
|
Unix Shell Linux
LeetCode刷题 Shell编程四则 | 194. 转置文件 192. 统计词频 193. 有效电话号码 195. 第十行
本文提供了几个Linux shell脚本编程问题的解决方案,包括转置文件内容、统计词频、验证有效电话号码和提取文件的第十行,每个问题都给出了至少一种实现方法。
339 6
LeetCode刷题 Shell编程四则 | 194. 转置文件 192. 统计词频 193. 有效电话号码 195. 第十行