力扣解题-88. 合并两个有序数组

力扣解题-88. 合并两个有序数组

给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。

请你 合并 nums2 到 nums1 中 ,使合并后的数组同样按 非递减顺序 排列。

注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。

示例 1:

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

输出:1,2,2,3,5,6

解释:需要合并 1,2,32,5,6

合并结果是 1,2,2,3,5,6 ,其中斜体加粗标注的为 nums1 中的元素。

示例 2:

输入:nums1 = 1, m = 1, nums2 = \[\], n = 0

输出:1

解释:需要合并 1 和 \[\] 。

合并结果是 1

示例 3:

输入:nums1 = 0, m = 0, nums2 = 1, n = 1

输出:1

解释:需要合并的数组是 \[\] 和 1

合并结果是 1

注意,因为 m = 0 ,所以 nums1 中没有元素。nums1 中仅存的 0 仅仅是为了确保合并结果可以顺利存放到 nums1 中。

提示:

nums1.length == m + n

nums2.length == n

0 <= m, n <= 200

1 <= m + n <= 200

-109 <= nums1i, nums2j <= 109


第一次解答

解题思路

核心方法:合并后整体排序,先将nums2的元素直接填充到nums1的末尾空闲位置,再对整个nums1数组进行排序,逻辑极简但未利用数组"有序"的特性,存在性能浪费。

具体步骤:

  1. 填充nums2到nums1:遍历nums2的每个元素(共n个),将nums2i赋值到nums1m+i的位置(nums1的前m位是有效元素,后n位是预留的0,正好用于存放nums2);
  2. 整体排序 :调用Arrays.sort(nums1)对合并后的nums1进行升序排序,完成非递减排列的要求。

性能劣势说明

该解法虽然实现了核心功能,但未利用题目中"两个数组均为非递减顺序"的关键条件,导致性能较差:

  1. 时间复杂度较高 :排序的时间复杂度为O((m+n)log(m+n)),而利用有序特性的最优解法可做到O(m+n),对于m+n=200的场景,排序的额外开销会导致耗时增加(4ms仅击败15.63%用户);
  2. 冗余操作:明明两个数组已有序,却重新对所有元素排序,相当于"抛弃已知有序信息,从头排序",是对题目条件的浪费;
  3. 内存表现一般:虽然无额外空间开销,但排序过程中JVM的排序算法(双轴快排)会产生少量临时空间,导致内存消耗43.2MB仅击败36.95%用户。

执行耗时:4 ms,击败了15.63% 的Java用户

内存消耗:43.2 MB,击败了36.95% 的Java用户

java 复制代码
    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);
    }

第二次解答

解题思路

核心方法:逆向双指针(从后往前合并),充分利用两个数组"非递减有序"的特性,从最大值开始比较,将较大值依次放入nums1的末尾,避免覆盖未处理的有效元素,时间复杂度优化至O(m+n),是本题的最优解法。

核心原理铺垫

由于nums1的末尾有n个空闲位置,且两个数组均为升序排列,从后往前合并 可避免"正向合并时覆盖nums1前m位有效元素"的问题:

  • 正向合并需额外空间暂存nums1的元素(否则会被覆盖),而逆向合并直接利用nums1的空闲末尾,无需额外空间;
  • 比较两个数组的"当前最大值",将更大的数放入nums1的当前最末尾,逐步向前填充,最终得到有序数组。
具体步骤
  1. 边界判断:若n=0(nums2为空),无需处理,直接返回(避免无效的指针操作);
  2. 初始化逆向双指针
    • p1 = m - 1:指向nums1有效元素的最后一个位置(即nums1的最大值位置);
    • p2 = n - 1:指向nums2的最后一个位置(即nums2的最大值位置);
    • p = m + n - 1:指向nums1的最后一个位置(即合并后元素的写入位置);
  3. 逆向合并核心循环p1 >= 0 && p2 >= 0):
    • 比较nums1p1和nums2p2的大小,取较大值写入nums1p
    • 若nums1p1 >= nums2p2:将nums1p1写入nums1p,p1左移一位(p1--),继续比较nums1的下一个最大值;
    • 否则:将nums2p2写入nums1p,p2左移一位(p2--),继续比较nums2的下一个最大值;
    • 每次写入后,p左移一位(p--),准备写入下一个元素;
  4. 处理nums2剩余元素 :若循环结束后p2 >= 0(说明nums2还有未合并的元素,且这些元素均小于nums1已合并的所有元素),将nums2p2依次写入nums1p,直到p2 < 0;
    • 无需处理nums1的剩余元素:因为nums1的剩余元素本就位于nums1的前半部分,且已是非递减顺序,无需额外操作。

核心优化逻辑说明

  1. 时间复杂度最优:仅需一次遍历(m+n次比较/赋值操作),时间复杂度为O(m+n),相比排序的O((m+n)log(m+n)),效率提升显著(0ms击败100%用户);
  2. 空间复杂度O(1):全程在nums1原数组上操作,无额外数组/集合创建,内存消耗降至42.9MB,击败90.89%用户;
  3. 避免元素覆盖:逆向合并的核心优势是"写入位置在未处理元素的后方",不会覆盖nums1前m位的有效元素,无需额外空间暂存数据;
  4. 边界处理完善 :通过if(n!=0)跳过nums2为空的情况,通过最后的p2循环处理nums2剩余元素,覆盖了m=0(nums1无有效元素)等极端场景。

执行耗时:0 ms,击败了100.00% 的Java用户

内存消耗:42.9 MB,击败了90.89% 的Java用户

java 复制代码
public void merge(int[] nums1, int m, int[] nums2, int n) {
        if(n!=0) {
            int p1 = m - 1;      // nums1 有效部分的最后一个索引
            int p2 = n - 1;      // nums2 最后一个索引
            int p = m + n - 1;   // nums1 总数组的最后一个索引(写入位置)
            while (p1 >= 0 && p2 >= 0) {
                int a = nums1[p1];
                int b = nums2[p2];
                if (a >= b) {
                    nums1[p] = a;
                    p1--;
                } else {
                    nums1[p] = b;
                    p2--;
                }
                p--;
            }
            //如果nums2的元素还有剩余,全部挪过去
            while (p2 >= 0) {
                nums1[p] = nums2[p2];
                p--;
                p2--;
            }
        }
    }

总结

  1. 第一次解答的"合并后排序"思路极简,但未利用数组"有序"的特性,时间复杂度较高,仅适合快速实现功能的场景;
  2. 第二次解答的"逆向双指针"是本题的最优解:利用有序特性将时间复杂度优化至O(m+n),且原地操作无额外空间开销,是对题目条件的充分利用;
  3. 本题的核心解题技巧是逆向思维:避开正向合并的"覆盖风险",从最大值开始填充,既利用nums1的空闲空间,又保证有序性。
相关推荐
1000世界小札5 小时前
《大话数据结构》第9章精读:归并排序与快速排序完整 C++ 实现
数据结构·c++·算法
2601_956121978 小时前
背包基础篇(01、完全、分组、多重、混合)
c++·算法·动态规划
SomeB1oody8 小时前
【RustyML入门】6.3. 并行归约
开发语言·后端·机器学习·rust·教程
兴通物联科技9 小时前
SMT PCB 微小 DataMatrix 码扫不动问题分析 兴通 XT8601B 600 万像素工业读码器落地实践
大数据·人工智能·单片机·嵌入式硬件·算法·计算机视觉
Profile排查笔记9 小时前
指纹浏览器哪个好?从 Profile、代理、权限和自动化能力判断是否适合
前端·人工智能·后端·自动化
用户938515635079 小时前
用 AI 结对编程从 0 搭一个"单词后台管理系统":Next.js + Supabase + Drizzle + shadcn/ui 全记录
后端·postgresql·next.js
爱学习的小邓同学10 小时前
Golang --- (1)第一个Golang程序
开发语言·后端·golang
小灰灰搞电子11 小时前
Rust+Slint 实现的“DNA双螺旋”加载动画源码分享
后端·rust·slint·加载动画
面向Google编程11 小时前
向量库不再囤数据:Milvus 3.0 零拷贝直读数据湖
后端