每日LeetCode:合并两个有序数组

在数据处理和算法设计中,有序数组合并是一个基础而重要的问题。本文将带你深入剖析 LeetCode 经典题目「合并两个有序数组」,逐步优化解法。

问题描述

给定两个按 非递减顺序 排列的整数数组 nums1nums2

  • nums1 长度为 m + n,前 m 个元素有效,后 n 个元素为占位值 0
  • nums2 长度为 n
  • 需将 nums2 合并到 nums1 中,使合并后数组保持 非递减顺序
  • 要求 :直接修改 nums1,不返回新数组
css 复制代码
输入: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. 调用内置排序函数排序
javascript 复制代码
const merge = (nums1, m, nums2, n) => {
    // 复制 nums2 到 nums1 尾部
    for (let i = 0; i < n; i++) {
        nums1[m + i] = nums2[i];
    }
    // 原地排序
    nums1.sort((a, b) => a - b);
};

时间复杂度分析

  • 复制操作:O(n)
  • 排序操作:O((m+n)log(m+n))
  • 总时间复杂度:O((m+n)log(m+n))

空间复杂度:O(1)(原地排序)

缺点:未利用数组有序特性,排序开销大

解法二:正向双指针(额外空间)

核心思想:利用双指针遍历有序数组,使用临时数组存储结果

算法步骤

  1. 创建临时数组 temp
  2. 初始化双指针 p1=0(nums1),p2=0(nums2)
  3. 比较指针值,取较小值放入 temp
  4. 处理剩余元素
  5. 复制 tempnums1
javascript 复制代码
const merge = (nums1, m, nums2, n) => {
    // 创建临时数组
    const temp = new Array(m + n);
    let p1 = 0, p2 = 0, t = 0;
    
    // 合并有序部分
    while (p1 < m && p2 < n) {
        if (nums1[p1] <= nums2[p2]) {
            temp[t++] = nums1[p1++];
        } else {
            temp[t++] = nums2[p2++];
        }
    }
    
    // 处理 nums1 剩余
    while (p1 < m) temp[t++] = nums1[p1++];
    
    // 处理 nums2 剩余
    while (p2 < n) temp[t++] = nums2[p2++];
    
    // 复制回 nums1
    for (let i = 0; i < m + n; i++) {
        nums1[i] = temp[i];
    }
};

时间复杂度:O(m+n)(遍历所有元素一次)

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

缺点:空间复杂度不满足最优要求

知识点扩展:双指针技巧 双指针是处理有序数组的高效技巧,常见模式:

  1. 同向指针:解决滑动窗口问题
  2. 相向指针:解决两数之和等问题
  3. 快慢指针:解决链表环检测问题

解法三:逆向双指针(最优解)

核心思想 :利用 nums1 尾部空闲空间,从后向前填充较大值

算法步骤

  1. 初始化三指针:
    • p1 = m-1(nums1 有效末尾)
    • p2 = n-1(nums2 末尾)
    • p = m+n-1(合并位置)
  2. 从后向前比较并填充
  3. 处理 nums2 剩余元素
javascript 复制代码
const merge = (nums1, m, nums2, n) => {
    let p1 = m - 1;    // nums1 有效部分末尾
    let p2 = n - 1;    // nums2 末尾
    let p = m + n - 1; // 合并位置
    
    // 从后向前比较并填充
    while (p1 >= 0 && p2 >= 0) {
        if (nums1[p1] > nums2[p2]) {
            nums1[p--] = nums1[p1--];
        } else {
            nums1[p--] = nums2[p2--];
        }
    }
    
    // 处理 nums2 剩余元素
    while (p2 >= 0) {
        nums1[p--] = nums2[p2--];
    }
};

时间复杂度:O(m+n)(每个元素仅处理一次)

空间复杂度:O(1)(完全原地操作)

关键点解析

  1. 逆向操作:避免覆盖未比较元素
  2. 尾部空闲空间:利用 nums1 的预留空间
  3. 剩余处理:只需处理 nums2 剩余元素

为什么不需要处理 nums1 剩余?

  • 当 nums2 元素处理完后,nums1 剩余元素已处于正确位置
  • 这些元素是全局最小值且位置正确

算法比较总结

解法 时间复杂度 空间复杂度 核心优势 适用场景
暴力排序 O((m+n)log(m+n)) O(1) 实现简单 快速原型开发
正向双指针 O(m+n) O(m+n) 利用有序特性 无空间限制场景
逆向双指针 O(m+n) O(1) 原地操作,空间最优 内存敏感环境

合并有序数组看似简单,却蕴含着分治思想、双指针技巧和空间优化策略。掌握这一基础算法,将为处理更复杂的有序数据问题奠定坚实基础。

相关推荐
绝无仅有33 分钟前
企微审批对接错误与解决方案
后端·算法·架构
中微子1 小时前
JavaScript 防抖与节流:从原理到实践的完整指南
前端·javascript
天天向上10241 小时前
Vue 配置打包后可编辑的变量
前端·javascript·vue.js
用户5040827858391 小时前
1. RAG 权威指南:从本地实现到生产级优化的全面实践
算法
芬兰y2 小时前
VUE 带有搜索功能的穿梭框(简单demo)
前端·javascript·vue.js
小蜜蜂dry2 小时前
Fetch 笔记
前端·javascript
拾光拾趣录2 小时前
列表分页中的快速翻页竞态问题
前端·javascript
vvilkim2 小时前
Nuxt.js 全面测试指南:从单元测试到E2E测试
开发语言·javascript·ecmascript
OpenTiny社区2 小时前
盘点字体性能优化方案
前端·javascript