LeetCode 88 合并两个有序数组

1. 题目

88. 合并两个有序数组 - 力扣(LeetCode)

题目描述

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

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

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

示例

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

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

约束

  • nums1.length == m + n

  • nums2.length == n

  • 0 <= m, n <= 200

  • 1 <= m + n <= 200

  • -10^9 <= nums1i, nums2j <= 10^9

2. 最佳解题思路描述(双指针从后往前,原地 O (1) 空间)

  1. 设三个指针:

    • i:指向 nums1 有效数字末尾(i = m - 1)

    • j:指向 nums2 末尾(j = n - 1)

    • k:指向 nums1 总长度末尾填充位置(k = m + n - 1)

  2. 从后向前比较两个数组有效数字,把更大的值放到 nums1[k],对应指针向前移动;

  3. 循环直到其中一个数组遍历完毕;

  4. 若 nums2 还有剩余更小数字,全部依次拷贝到 nums1 前面空位;

  5. 全程原地修改 nums1,不开辟额外数组,空间复杂度 O (1),时间 O (m+n)。

优势

  • 无需频繁数组插入 / 删除,避免数组元素批量移动带来的时间损耗;

  • 原地操作,不额外开辟数组,空间最优;

  • 一次遍历完成合并,效率最高。

3. 我的可优化代码(逻辑可 AC 但性能差)

复制代码
class Solution {
public:
    void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
        int t = 0,t2 = 0;
        for(int j=0;j<n;j++){
            for(int i=t;i<m+n;i++){
                if(nums1[i]>=nums2[j]){
                    int p = nums2[j];
                    nums1.insert(nums1.begin() + i, p);
                    t++;
                    i--;
                    break;
                }
            }
        }
        while(t<n){
            int q = nums2[t];
            nums1.insert(nums1.begin()+t+m,q);
            t++;
        }
        nums1.erase(nums1.begin() + n+m, nums1.begin() + n+m+n);
    }
};

代码说明

  1. 逻辑正确性:

    外层遍历 nums2 每个数字,内层从上次插入位置向后查找第一个大于等于当前 nums2 j 的位置,执行 insert 插入;

    若 nums2 剩余数字全部大于 nums1 所有有效数,直接追加到末尾;

    最后 erase 删除末尾原本占位的 n 个 0,最终数组有序,可以通过全部测试用例。

  2. 存在的缺陷与可优化点:

    • 性能极差:vector::insert 会导致插入点后所有元素批量后移,嵌套循环时间复杂度接近 O ((m+n)*n),数据量大时超时;

    • 额外内存开销:insert 会频繁扩容、拷贝数组元素;

    • 冗余操作:最后执行 erase 删除占位 0,多一次数组截断操作;

    • 双重循环嵌套,代码冗长,可读性差。

4. 最优标准代码(双指针逆序原地合并)

复制代码
class Solution {
public:
    void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
        int i = m - 1; // nums1有效末尾
        int j = n - 1; // nums2末尾
        int k = m + n - 1; // 填充位置末尾
        while(i >= 0 && j >= 0){
            if(nums1[i] > nums2[j]){
                nums1[k] = nums1[i];
                i--;
            }else{
                nums1[k] = nums2[j];
                j--;
            }
            k--;
        }
        // nums2剩余数字全部拷贝到前面
        while(j >= 0){
            nums1[k] = nums2[j];
            j--;
            k--;
        }
    }
};

5. 总结

  1. 我的代码逻辑正确可 AC,但属于暴力插入写法,依靠 vector 插入 API 完成合并,时间、空间性能差,不推荐面试使用;

  2. 本题最优解法核心:从后往前双指针,利用 nums1 尾部预留空位,避免元素反复移位;

  3. 关键记忆点:

    • 数组插入 insert 是重操作,算法题尽量避免频繁嵌套插入;

    • 两个有序数组合并优先考虑双指针;

    • nums1 尾部有空白时,逆序填充可以完美规避覆盖未读取数据的问题。

  4. 适用场景区分:

    • 刷题随便过用你的插入写法;

    • 面试、追求性能必须使用逆序双指针版本。

6. 相关知识拓展

拓展 1:正向双指针需要额外数组

如果从前向后合并,nums1 前面数字会被覆盖丢失,必须新建临时数组存储结果,空间 O (m+n),不如逆序方案。

拓展 2:vector insert/erase 底层代价

vector 连续内存,中间插入会移动后方全部元素,时间 O (N);尾部插入无移位代价。本题你在数组中间频繁 insert,是性能瓶颈根源。

拓展 3:另一种偷懒写法(库函数,面试不推荐)

复制代码
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
    for(int i=0;i<n;i++) nums1[m+i] = nums2[i];
    sort(nums1.begin(),nums1.end());
}

直接把 nums2 复制到 nums1 尾部再整体排序,代码极简,但排序时间 O ((m+n) log (m+n)),效率低于双指针。

拓展 4:复杂度对比

  • 我的插入写法:时间 O (n*(m+n)),空间不稳定(vector 扩容);

  • sort 偷懒写法:时间 O ((m+n) log (m+n)),空间 O (1);

  • 逆序双指针最优解:时间 O (m+n),空间 O (1)。

相关推荐
数据皮皮侠AI1 小时前
上市公司数字供应链金融指数(2010-2024)
大数据·人工智能·算法
Hi李耶2 小时前
【LeetCode】541.反转字符串 II
算法·leetcode·职场和发展
shylyly_2 小时前
104.二叉树的最大深度
数据结构·算法·104.二叉树的最大深度
AI科技星3 小时前
曲率‑挠率与 $\boldsymbol{\omega/c}$ 的关系、精算验证及其物理意义
c语言·开发语言·线性代数·算法·决策树·机器学习·ai科技星
沐籽李3 小时前
HuDiff在抗体人源化项目中的工程化落地
人工智能·算法·aidd·抗体设计
依然范特东3 小时前
强化学习笔记6--IS、PPO、TD、DQN、Actor-Critic
笔记·算法
一次旅行5 小时前
RLHF全链路深度解析:Reward Model数学推导+PPO完整实战,对比GRPO轻量化方案
人工智能·算法·机器学习
Wang's Blog5 小时前
AI Agent白手起家29: Few Shot 提示词工程实战
人工智能·算法
June`6 小时前
warp shuffle指令
c++·人工智能·算法·cuda