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

LeetCode 面试经典 150 题:合并两个有序数组(双指针解法详解)

在算法面试中,“合并有序数组” 是一道高频基础题,不仅考察对数组操作的熟练度,更能体现对 “双指针” 这一核心技巧的理解。本文将带大家深入剖析这道题的最优解法,从题目分析到思路推导,再到复杂度解读,帮你彻底掌握这类问题的解题逻辑。

一、题目链接与题干解读

首先,大家可以通过以下链接直接访问题目,先自行思考解题思路:

LeetCode 题目链接: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]

解释:需要将 nums2 的 3 个元素融入 nums1,最终保持非递减顺序。

二、解题思路:双指针(从后向前遍历)

拿到这道题,很多人第一反应是 “从前往后遍历”—— 比较 nums1 和 nums2 的开头元素,把小的放进新数组。但这样有个问题:如果直接在 nums1 上操作,前面的元素可能会被覆盖;如果用额外数组存结果,又会增加空间复杂度。

“从后向前遍历” 的双指针法,完美解决了这两个问题:既不需要额外空间,又能避免元素覆盖。下面我们一步步拆解思路。

1. 指针初始化

我们需要三个指针,分别指向三个关键位置:

  • 指针 i:指向 nums1 中 “有效元素” 的末尾(即第 m-1 个索引,因为数组从 0 开始),代表当前 nums1 中待比较的最大元素;
  • 指针 j:指向 nums2 的末尾(即第 n-1 个索引),代表当前 nums2 中待比较的最大元素;
  • 指针 k:指向 nums1 整体的末尾(即第 m+n-1 个索引),代表合并后数组的 “待填充位置”。

用示例 1 举例,初始时三个指针的位置如下:

  • nums1 = [1,2,3,0,0,0],i = 2(指向元素 3);
  • nums2 = [2,5,6],j = 2(指向元素 6);
  • k = 5(指向 nums1 最后一个 0 的位置)。

2. 核心遍历逻辑

我们的目标是:每次从 nums1[i]nums2[j] 中选较大的元素,放到 nums1[k] 的位置,然后对应指针向前移动,直到所有元素都填充完毕。

具体步骤分为两种情况:

情况 1:两个数组都还有未比较的元素(i >= 0 且 j >= 0)
  • 比较 nums1[i] 和 nums2[j]:
    • 如果 nums1[i] >= nums2[j]:说明 nums1[i] 是当前最大的元素,把它放到 nums1[k] 的位置;然后 i--(nums1 待比较元素向前移)、k--(待填充位置向前移);
    • 如果 nums1[i] < nums2[j]:说明 nums2[j] 是当前最大的元素,把它放到 nums1[k] 的位置;然后 j--(nums2 待比较元素向前移)、k--。

用示例 1 走一遍这个过程:

  • 第一次比较:nums1[2] = 3 vs nums2[2] = 6 → 6 更大,把 6 放到 nums1[5],此时 nums1 = [1,2,3,0,0,6],j=1,k=4;
  • 第二次比较:nums1[2] = 3 vs nums2[1] = 5 → 5 更大,把 5 放到 nums1[4],此时 nums1 = [1,2,3,0,5,6],j=0,k=3;
  • 第三次比较:nums1[2] = 3 vs nums2[0] = 2 → 3 更大,把 3 放到 nums1[3],此时 nums1 = [1,2,3,3,5,6],i=1,k=2;
  • 第四次比较:nums1[1] = 2 vs nums2[0] = 2 → 相等,选 nums1[1] 放到 nums1[2],此时 nums1 = [1,2,2,3,5,6],i=0,k=1;
  • 第五次比较:nums1[0] = 1 vs nums2[0] = 2 → 2 更大,把 2 放到 nums1[1],此时 nums1 = [1,2,2,3,5,6],j=-1,k=0。
情况 2:其中一个数组已遍历完
  • 如果 j < 0(nums2 已遍历完):说明剩下的 nums1 元素都是最小的,无需再操作(因为 nums1 本身就是有序的);
  • 如果 i < 0(nums1 已遍历完):说明 nums2 剩下的元素都是最小的,需要把 nums2 中 j >= 0 的元素依次放到 nums1[k] 中,直到 j < 0。

比如另一个示例:nums1 = [0],m = 0;nums2 = [1],n = 1。此时 i = -1,直接把 nums2[0] 放到 nums1[0],结果就是 [1]。

3. 循环终止条件

当 k < 0 时,说明 nums1 的所有位置都已填充完毕,循环结束。

三、复杂度分析

1. 时间复杂度:O (m + n)

  • 我们只遍历了 nums1 的 m 个有效元素和 nums2 的 n 个元素,每个元素只比较和移动一次,没有嵌套循环;
  • 无论哪种情况,总操作次数都是 m + n 次,因此时间复杂度是线性的。

2. 空间复杂度:O (1)

  • 整个过程只用到了 i、j、k 三个指针,没有额外开辟数组或其他数据结构;
  • 所有操作都在原数组 nums1 上进行,因此空间复杂度是常数级的。

四、代码实现

根据上面的思路,我们可以写出简洁的代码。核心逻辑就是围绕三个指针的移动和元素比较,以下是代码框架(你可以根据自己熟悉的编程语言填充具体实现):

1,Python3

class Solution:def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:k = m + n - 1i, j = m - 1, n - 1while j >= 0:if i >= 0 and nums1[i] > nums2[j]:nums1[k] = nums1[i]i -= 1else:nums1[k] = nums2[j]j -= 1k -= 1 

2,Java

class Solution {public void merge(int[] nums1, int m, int[] nums2, int n) {for (int i = m - 1, j = n - 1, k = m + n - 1; j >= 0; --k) {nums1[k] = i >= 0 && nums1[i] > nums2[j] ? nums1[i--] : nums2[j--];}}
}

你可以将上述代码复制到 LeetCode 编辑器中,结合题目示例进行测试,验证是否正确。

五、总结与拓展

这道题的关键在于 “利用数组的有序性” 和 “从后向前遍历”—— 前者避免了无序数组的复杂排序,后者解决了元素覆盖和额外空间的问题。双指针法是数组操作中的核心技巧,类似的思路还能用于 “移除元素”“两数之和(有序数组)” 等题目。

比如思考一个拓展问题:如果两个数组是递减有序的,如何合并成一个递减有序的数组?其实逻辑完全相通,只需把 “从后向前” 改成 “从前向后”,比较时选较小的元素即可。

希望通过本文的拆解,你能不仅掌握这道题的解法,更能理解双指针法的本质 —— 通过指针移动减少重复操作,优化时间和空间复杂度。


文章转载自:

http://jSfuDsyK.zcncb.cn
http://mYPzfUYw.zcncb.cn
http://Mz32rQox.zcncb.cn
http://6SHxnHGO.zcncb.cn
http://QawxucxV.zcncb.cn
http://Zw0cngOE.zcncb.cn
http://lyLybYRd.zcncb.cn
http://KwBGK7au.zcncb.cn
http://jq2out6B.zcncb.cn
http://3AyVLwXo.zcncb.cn
http://hglLsHNb.zcncb.cn
http://g6MdA3Vw.zcncb.cn
http://WRV5bAZv.zcncb.cn
http://IeLX9gqn.zcncb.cn
http://TRasNWqg.zcncb.cn
http://kcLlBTU0.zcncb.cn
http://C324J5lw.zcncb.cn
http://NK3Ysuk9.zcncb.cn
http://gxkeRrjo.zcncb.cn
http://btZ7BfKS.zcncb.cn
http://ROCBdvtg.zcncb.cn
http://ijE1oblB.zcncb.cn
http://OAEOtAEp.zcncb.cn
http://VwANASHz.zcncb.cn
http://YwE7JSbi.zcncb.cn
http://7GD6Anu6.zcncb.cn
http://ExYKwX4D.zcncb.cn
http://7lMdR0iu.zcncb.cn
http://ULrPa3Ak.zcncb.cn
http://cxuAqlYO.zcncb.cn
http://www.dtcms.com/a/371672.html

相关文章:

  • Nestjs框架: 基于策略的权限控制(ACL)与数据权限设计
  • Go语言实战案例-实现简易定时提醒程序
  • 如何在项目中使用 Claude 记忆库系统(二开场景指南)
  • Matlab Simulink中的一些记录
  • 在Word和WPS文字的表格中快速输入连续的星期、月、日
  • Linux 周期性用户作业计划:crontab
  • Flink TaskManager日志时间与实际时间有偏差
  • 综合案列(SQLpymysql)
  • 得物后端二面
  • v$lock TS lock id1 用于发现Oracle pdb不能关闭的sid
  • Lenovo联想YOGA Pro 16 IAH10 2025款笔记本电脑(83L0)开箱状态预装OEM原厂Win11系统
  • 硬件-电容学习DAY3——钽电容制造全解析:从粉末到精品的奥秘
  • word2vec模型案例
  • Python将md转html,转pdf
  • 虚拟机之CentOS、网络设置的有趣问题
  • Java全栈开发面试实录:从基础到高阶技术深度解析
  • Java捕获子线程异常以及主线程感知子线程异常
  • 设计模式:模板方法模式(Template Method Pattern)
  • BeautifulSoup4用法及示例
  • 宋红康 JVM 笔记 Day13|String Table
  • C/C++---变量对象的创建 栈与堆
  • 《AI大模型应知应会100篇》第69篇:大模型辅助的数据分析应用开发
  • 基于「YOLO目标检测 + 多模态AI分析」的PCB缺陷检测分析系统(vue+flask+数据集+模型训练)
  • SpringAMQP 的发布方确认
  • 2.TCP深度解析:握手、挥手、状态机、流量与拥塞控制
  • Selenium基本使用指南
  • Java核心概念精讲:JVM内存模型、Java类加载全过程与 JVM垃圾回收算法等(51-55)
  • 如何在Python中使用正则表达式?
  • Git Bash 中 Git 命令的实用主义指南
  • Vue → React/Next.js 思维对照表