LeetCode 88 - 合并两个有序数组

news/2025/3/4 21:57:41/

LeetCode 88 - 合并两个有序数组 是非常基础的数组操作题目,考察双指针、逆序操作和空间优化等技巧。这个问题相当经典,对后续的归并排序、多指针问题、双数组相关问题都有指导意义。以下是详细的解法、模板与变体问题讲解。


题目描述

给定你两个有序整数数组 nums1nums2,以及两个非负整数 mn,表示分别代表数组 nums1nums2 中的有效元素个数。

nums2 中的所有元素合并到 nums1 中,使数组变为一个有序数组。

  • 你需要在 原地 修改 nums1,即不能使用额外空间。

示例

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

解法及分析

解法 1:双指针,从后向前逆序合并

核心思路
  1. 从两个数组的后端(即较大的元素)开始合并:

    • 比较 nums1[m-1]nums2[n-1],将较大的数填充到 nums1 的最后位置,即索引 m + n - 1
    • 每次填充一个元素,然后移动相应的指针。
    • 优化方向:因为 nums1 末尾已经预留了足够空间,所以可以从 后向前 直接覆盖。
  2. 特殊情况处理:

    • 如果 nums2 元素还未完全合并(n > 0),需要将剩余的 nums2 元素直接拷贝到 nums1(最终结果仍是有序的)。
    • 如果 nums1 已完全扫描完,则只需要简单地覆盖。
模板代码
class Solution {public void merge(int[] nums1, int m, int[] nums2, int n) {int p1 = m - 1;            // 指针指向 nums1 的有效部分末尾int p2 = n - 1;            // 指针指向 nums2 的末尾int p = m + n - 1;         // 指针指向 nums1 的总长度末尾// 从后向前合并while (p1 >= 0 && p2 >= 0) {if (nums1[p1] > nums2[p2]) {nums1[p--] = nums1[p1--]; // 将较大值放到 nums1 的末尾} else {nums1[p--] = nums2[p2--];}}// 如果 nums2 尚未排完,直接补充到 nums1while (p2 >= 0) {nums1[p--] = nums2[p2--];}}
}
复杂度分析
  • 时间复杂度: O(m + n),每个元素只需遍历一次。
  • 空间复杂度: O(1),原地操作,不使用额外空间。
  • 适用场景: 空间受限且数组有序。

解法 2:双指针,从前向后合并(额外空间)

核心思路
  1. 如果允许使用额外空间,可以简单地创建一个临时数组来存储合并结果:

    • 利用两个指针分别扫描 nums1nums2 的有效部分。
    • 比较当前指针指向的元素,将较小值存入新的数组。
    • 最后将临时数组的元素拷贝回 nums1
  2. 问题:此方法不符合“原地”修改的要求,因此主要用于理解合并逻辑。

模板代码
class Solution {public void merge(int[] nums1, int m, int[] nums2, int n) {int[] sorted = new int[m + n]; // 新数组int p1 = 0, p2 = 0, p = 0;// 合并两个数组while (p1 < m && p2 < n) {if (nums1[p1] <= nums2[p2]) {sorted[p++] = nums1[p1++];} else {sorted[p++] = nums2[p2++];}}// 处理剩余元素while (p1 < m) {sorted[p++] = nums1[p1++];}while (p2 < n) {sorted[p++] = nums2[p2++];}// 将结果拷贝回 nums1System.arraycopy(sorted, 0, nums1, 0, m + n);}
}
复杂度分析
  • 时间复杂度: O(m + n),只需一趟扫描。
  • 空间复杂度: O(m + n),需要额外数组来保存结果。
  • 适用场景: 构造新数组后再修改原数组的场景。

解法 3:Java 内置排序

核心思路
  1. nums2 的有效元素直接拷贝到 nums1 的尾部:
    nums1[m..m+n] = nums2[0..n]
    
  2. 使用 Java 提供的内置排序方法,将 nums1 排序。

虽然算法简单,但不符合“原地操作”限制,可能会对排序效率和内存有额外要求。

模板代码
import java.util.Arrays;class Solution {public void merge(int[] nums1, int m, int[] nums2, int n) {// 将 nums2 拷贝到 nums1 的后面System.arraycopy(nums2, 0, nums1, m, n);// 排序 nums1Arrays.sort(nums1);}
}
复杂度分析
  • 时间复杂度: O((m + n) log (m + n)),由排序函数的复杂度决定。
  • 空间复杂度: O(m + n),排序中可能需要额外空间。
  • 适用场景: 快速验证时使用,但不适合真正工程开发。

经典变体问题


变体 1:合并两个有序链表

问题背景:

  • 给定两个升序链表,将它们合并为一个升序链表,返回头节点。
  • 这个问题与合并数组的逻辑非常相似,但需要操作指针。
模板代码
class Solution {public ListNode mergeTwoLists(ListNode l1, ListNode l2) {ListNode dummy = new ListNode(-1); // 哑节点ListNode current = dummy;// 合并链表while (l1 != null && l2 != null) {if (l1.val <= l2.val) {current.next = l1;l1 = l1.next;} else {current.next = l2;l2 = l2.next;}current = current.next;}// 连接剩余部分current.next = (l1 != null) ? l1 : l2;return dummy.next;}
}

变体 2:寻找两个有序数组的中位数

问题背景:

  • 输入是两个升序数组,找到它们合并后的中位数。
  • 与本题不同,此处需要合并逻辑在不显式合并的情况下完成。
解决方法: 可使用双指针法:
class Solution {public double findMedianSortedArrays(int[] nums1, int[] nums2) {int m = nums1.length, n = nums2.length;int left = (m + n + 1) / 2;int right = (m + n + 2) / 2;return (findK(nums1, 0, nums2, 0, left) + findK(nums1, 0, nums2, 0, right)) / 2.0;}private int findK(int[] nums1, int i, int[] nums2, int j, int k) {if (i >= nums1.length) return nums2[j + k - 1];if (j >= nums2.length) return nums1[i + k - 1];if (k == 1) return Math.min(nums1[i], nums2[j]);int mid1 = (i + k / 2 - 1 < nums1.length) ? nums1[i + k / 2 - 1] : Integer.MAX_VALUE;int mid2 = (j + k / 2 - 1 < nums2.length) ? nums2[j + k / 2 - 1] : Integer.MAX_VALUE;if (mid1 < mid2) {return findK(nums1, i + k / 2, nums2, j, k - k / 2);} else {return findK(nums1, i, nums2, j + k / 2, k - k / 2);}}
}

快速 AC 总结

  1. 双指针法是首选:
    • 从后向前合并(逆序操作)是最优解,时间 O(m + n),空间 O(1)。
  2. 理解链表和数组问题的共同逻辑:
    • 合并逻辑、双指针技巧可以直接应用在链表问题或排序问题上。
  3. 注意特例:
    • 例如其中一个数组为空时,不需要额外逻辑处理,直接完成即可(已覆盖于模板代码中)。
  4. 灵活应对变体问题:
    • 比如排序、寻找中位数问题,都可以通过拆解问题或类似合并的思想解决。

通过掌握双指针技巧和其相关变体题目,可以轻松快速解决这类问题!


http://www.ppmy.cn/news/1576654.html

相关文章

[IP] DDR_FIFO(DDR3 用户FIFO接口)

IP(DDR_FIFO)将DDR3 IP的用户侧复杂接口修改为简易的FIFO接口&#xff0c;用户侧更加简易例化使用MIG 核 IP介绍 c0_xx (连接DDR app接口) 此IP 仅需根据MIG配置进行有限修改&#xff0c;即可使用&#xff01; 关于IP详细使用说明&#xff0c;参考IP datasheet&#xff01; 示…

c++ Ranges Library使用笔记(简单说明)

c Ranges Library使用笔记&#xff08;简单说明&#xff09; 1. 数值适配器&#xff08;Range Adapters&#xff09;常用数值适配器示例代码 2. 生成器&#xff08;Generators&#xff09;常用生成器示例代码 3. 组合使用示例示例代码 总结数值适配器&#xff08;Range Adapter…

汽车轮胎损伤缺陷分割数据集labelme格式1957张3类别

数据集格式&#xff1a;labelme格式(不包含mask文件&#xff0c;仅仅包含jpg图片和对应的json文件) 图片数量(jpg文件个数)&#xff1a;1957 标注数量(json文件个数)&#xff1a;1957 标注类别数&#xff1a;3 标注类别名称:["cut","indentation",&quo…

NO.22十六届蓝桥杯备战|一维数组|七道练习|冒泡排序(C++)

B2093 查找特定的值 - 洛谷 题⽬要求下标是从0开始的&#xff0c;和数组的下标是吻合的&#xff0c;存放数据应该从下标0开始n的取值范围是1~10000数组中存放的值的绝对值不超10000&#xff0c;说明int类型就⾜够了找到了输出下标&#xff0c;找不到要输出-1&#xff0c;这⼀点…

Java面试第八山!《Spring框架》

一、Spring框架概述 Spring是Java企业级应用开发的核心框架&#xff0c;通过控制反转&#xff08;IoC&#xff09;和 面向切面编程&#xff08;AOP&#xff09;实现模块解耦&#xff0c;简化开发流程。其核心优势包括依赖注入、声明式事务管理、集成主流ORM框架&#xff08;如…

初阶数据结构(C语言实现)——3顺序表和链表(1)

目录 【本节目标】1. 线性表2.顺序表2.1概念及结构2.2 接口实现2.2.0 动态顺序表2.2.1 顺序表初始化SLInit&#xff08;&#xff09;2.2.2 销毁和打印2.2.3 尾插SLPushBack&#xff08;&#xff09;2.2.4 尾删SLPopBack&#xff08;&#xff09;2.2.5 头插2.2.6 头删2.2.7 插入…

Spring学习笔记03——Spring Boot的文件结构

Spring boot常见的文件结构&#xff1a; src/ ├── main/ │ ├── java/ │ │ └── com.example.demo/ │ │ ├── DemoApplication.java # 主入口 │ │ ├── config/ # 配置类 │ │ ├── controller/ …

MySQL实现文档全文搜索,分词匹配多段落重排展示,知识库搜索原理分享

一、背景 在文档搜索场景中&#xff0c;高效精准的搜索功能至关重要&#xff0c;能提升检索效率&#xff0c;为用户提供精准、快速的信息获取体验&#xff0c;提高工作效率。在文档管理系统里&#xff0c;全文搜索是非常重要的功能之一。随着文档数量增长&#xff0c;如何快速…