当前位置: 首页 > news >正文

合并两个有序数组的高效算法详解

合并两个有序数组的高效算法详解

  • **合并两个有序数组的高效算法详解**
    • **1. 问题描述**
    • **2. 常见解法分析**
      • **方法 1:合并后排序(暴力法)**
      • **方法 2:双指针法(额外空间)**
    • **3. 最优解法:双指针从后向前合并**
      • **核心思路**
      • **代码实现**
      • **复杂度分析**
      • **示例演示**
    • **4. 关键点总结**
    • **5. 扩展思考**
    • **6. 总结**


合并两个有序数组的高效算法详解

1. 问题描述

给定两个按 非递减顺序 排列的整数数组 nums1nums2,以及两个整数 mn,分别表示 nums1nums2 中的元素数目。

要求:

  • nums2 合并到 nums1 中,使合并后的数组仍然保持 非递减顺序
  • nums1 的初始长度为 m + n,其中后 n 个元素为 0,表示应被 nums2 填充的位置。

示例

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

2. 常见解法分析

方法 1:合并后排序(暴力法)

思路

  1. nums2 的所有元素直接放入 nums1 的末尾。
  2. 对整个 nums1 进行排序。

代码

public void merge(int[] nums1, int m, int[] nums2, int n) {for (int i = 0; i < n; i++) {nums1[m + i] = nums2[i];}Arrays.sort(nums1);
}

复杂度分析

  • 时间复杂度:O((m+n) log(m+n))(排序的时间复杂度)
  • 空间复杂度:O(1)(未使用额外空间)

缺点

  • 没有利用 nums1nums2 已经有序 的特性,导致时间复杂度较高。

方法 2:双指针法(额外空间)

思路

  1. 创建一个临时数组 temp,用两个指针分别遍历 nums1nums2,按顺序选择较小的元素放入 temp
  2. 最后将 temp 复制回 nums1

代码

public void merge(int[] nums1, int m, int[] nums2, int n) {int[] temp = new int[m + n];int i = 0, j = 0, k = 0;while (i < m && j < n) {if (nums1[i] <= nums2[j]) {temp[k++] = nums1[i++];} else {temp[k++] = nums2[j++];}}while (i < m) temp[k++] = nums1[i++];while (j < n) temp[k++] = nums2[j++];System.arraycopy(temp, 0, nums1, 0, m + n);
}

复杂度分析

  • 时间复杂度:O(m + n)
  • 空间复杂度:O(m + n)(需要额外数组)

缺点

  • 需要额外的 O(m+n) 空间,不符合题目对空间复杂度的优化要求。

3. 最优解法:双指针从后向前合并

核心思路

由于 nums1 的后面有足够的空间(填充 0),我们可以 从后向前 合并,避免覆盖 nums1 前面的数据。

步骤

  1. 初始化三个指针:
    • i = m - 1nums1 有效部分的末尾)
    • j = n - 1nums2 的末尾)
    • k = m + n - 1nums1 最终合并后的末尾)
  2. 比较 nums1[i]nums2[j],将较大的数放入 nums1[k],并移动指针。
  3. 如果 nums2 还有剩余元素,直接复制到 nums1 前面。

代码实现

public void merge(int[] nums1, int m, int[] nums2, int n) {int i = m - 1, j = n - 1, k = m + n - 1;while (j >= 0) {  // 只需要处理 nums2 剩余元素if (i >= 0 && nums1[i] > nums2[j]) {nums1[k--] = nums1[i--];} else {nums1[k--] = nums2[j--];}}
}

复杂度分析

  • 时间复杂度:O(m + n)(只需遍历 nums1nums2 各一次)
  • 空间复杂度:O(1)(原地修改,无额外空间)

示例演示

输入

nums1 = [1, 2, 3, 0, 0, 0], m = 3  
nums2 = [2, 5, 6], n = 3

执行过程

步骤ijknums1[i]nums2[j]操作nums1 结果
122536nums1[5] = 6[1,2,3,0,0,6]
221435nums1[4] = 5[1,2,3,0,5,6]
320332nums1[3] = 3[1,2,3,3,5,6]
410222nums1[2] = 2[1,2,2,3,5,6]
51-11--j < 0 退出循环合并完成

最终结果

[1, 2, 2, 3, 5, 6]

4. 关键点总结

  1. 为什么从后向前合并?

    • nums1 后面有足够的空间,不会覆盖未处理的元素。
    • 如果从前向后合并,需要额外空间存储被覆盖的数据。
  2. 为什么 while (j >= 0) 就够了?

    • 只要 nums2 合并完成,nums1 剩余部分已经在正确位置,无需处理。
  3. 边界情况处理

    • nums1 为空 → 直接复制 nums2
    • nums2 为空 → nums1 保持不变。

5. 扩展思考

  • 如果 nums1nums2 不是有序的,如何优化?
    → 先排序再合并,但时间复杂度会上升至 O((m+n) log(m+n))

  • 如果要求返回新数组而不是修改 nums1
    → 可以用 方法 2(双指针 + 额外空间)


6. 总结

方法时间复杂度空间复杂度适用场景
合并后排序O((m+n) log(m+n))O(1)简单但效率低
双指针(额外空间)O(m+n)O(m+n)需要新数组时
双指针(原地合并)O(m+n)O(1)最优解

推荐使用双指针从后向前合并,既高效又节省空间! 🚀

相关文章:

  • 1.1 认识编程与C++
  • 黑马k8s(七)
  • 腾讯开源实时语音大模型VITA-audio,92mstoken极速响应,支持多语言~
  • 麒麟v10 部署 MySQL 5.6.10 完整步骤
  • javaSE.迭代器
  • AI Agent开发第67课-彻底消除RAG知识库幻觉-文档分块全技巧(1)
  • 密码学刷题小记录
  • QML学习01(设置宽度、高度、坐标点、标题,信号与槽,键盘事件)
  • 网页渲染的两条赛道
  • 【高斯拟合】不用库手写高斯拟合算法:从最小二乘到拟合参数推导
  • 牛客网NC22012:判断闰年问题详解
  • [c语言日寄]数据结构:栈
  • RAGFlow 中的 Rerank 和 Recall 解释
  • 大数据架构选型全景指南:核心架构对比与实战案例 解析
  • 吊舱热敏传感器抗干扰技术分析!
  • mysqlbinlog用法详解
  • AI数字人融合VR全景:从技术突破到可信场景落地
  • LeetCode 235. 二叉搜索树的最近公共祖先 LeetCode 701.二叉搜索树中的插入操作 LeetCode 450.删除二叉搜索树中的节点
  • Logistics | Days of Inventory vs. Stock Days 【待续】
  • 射击游戏demo11
  • 浙江省委金融办原副主任潘广恩被“双开”
  • 美国关税压力下,日本经济一年来首次萎缩
  • “家国万里时光故事会” 举行,多家庭共话家风与家国情怀
  • 舱位已排到月底,跨境电商忙补货!美线订单大增面临爆舱,6月运价或翻倍
  • 共建医学人工智能高地,上海卫健委与徐汇区将在这些方面合作
  • 经常口干口渴的人,要当心这些病