每日一道算法题:合并数组

在这里插入图片描述

合并数组

给你两个按 非递减顺序 排列的整数数组 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,3] 和 [2,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和nums2的开始,比较指针所指的两个数,将较小的数添加到一个新的数组中,然后移动该指针。当一个数组被遍历完后,将另一个数组剩下的元素全部添加到新的数组中。最后再将新数组的元素复制回nums1。这个方法的时间复杂度是O(m+n),但是需要O(m+n)的额外空间。

双指针从后往前:为了避免使用额外的空间,我们可以从nums1和nums2的末尾开始,使用两个指针从后往前进行比较,并将较大的数从后往前填入nums1。这种方法同样的时间复杂度是O(m+n),但是不需要额外的空间。

归并排序的合并步骤:这题其实可以看做是归并排序中的合并步骤。我们可以使用归并排序合并两个有序数组的方法,也是通过双指针实现。其中,双指针从后往前的方法是最优的,因为它既满足了O(m+n)的时间复杂度,又没有使用额外的空间。


第一种解法:双指针从前往后

实现思路

  1. 使用两个指针p1p2分别初始化为nums1nums2的起始位置。
  2. 比较两个指针所指的元素,将较小的元素添加到一个新的数组中。
  3. 移动指向较小元素的指针。
  4. 当一个数组的元素被遍历完后,将另一个数组中的剩余元素全部添加到新的数组中。
  5. 最后,将新数组的元素复制到nums1中。

时间复杂度分析

  1. 在最坏的情况下,两个指针都会遍历其对应的数组一次,因此时间复杂度为O(m+n)。

空间复杂度分析

  1. 需要一个额外的数组存储合并后的结果,该数组的长度为m+n,因此空间复杂度为O(m+n)。

代码实现

public void merge(int[] nums1, int m, int[] nums2, int n) {
    int[] merged = new int[m + n]; // 创建一个新数组用于存储合并后的结果
    int p1 = 0; // 指向nums1的起始位置
    int p2 = 0; // 指向nums2的起始位置
    int p = 0;  // 指向merged的起始位置

    // 当两个数组都还有元素时,进行比较和合并
    while (p1 < m && p2 < n) {
        // 将较小的元素添加到merged中,并移动对应的指针
        if (nums1[p1] < nums2[p2]) {
            merged[p] = nums1[p1];
            p1++;
        } else {
            merged[p] = nums2[p2];
            p2++;
        }
        p++;
    }

    // 如果nums1还有剩余元素,将其全部添加到merged中
    while (p1 < m) {
        merged[p] = nums1[p1];
        p1++;
        p++;
    }

    // 如果nums2还有剩余元素,将其全部添加到merged中
    while (p2 < n) {
        merged[p] = nums2[p2];
        p2++;
        p++;
    }

    // 将merged的元素复制回nums1
    for (int i = 0; i < m + n; i++) {
        nums1[i] = merged[i];
    }
}

第二种解法:双指针从后往前

实现思路

  1. 使用两个指针p1p2,分别初始化为nums1nums2的有效元素的末尾位置。
  2. 设置一个指针tail,指向nums1数组的末尾。
  3. 从后往前比较p1p2指向的元素,将较大的元素放到tail所指的位置。
  4. 移动较大元素对应的指针,并将tail指针前移。
  5. 重复上述过程,直至p1p2任一指针移动到数组起始位置之前。
  6. 如果nums2数组还有元素未被考虑,将其剩余元素复制到nums1的前部。

时间复杂度分析

  1. p1p2两个指针最多会遍历其对应的数组一次,所以时间复杂度为O(m+n)。

空间复杂度分析

  1. 本方法没有使用额外的存储空间,只用了几个辅助指针变量,因此空间复杂度为O(1)。

代码实现

public void merge(int[] nums1, int m, int[] nums2, int n) {
    int p1 = m - 1;   // 指向nums1有效部分的末尾
    int p2 = n - 1;   // 指向nums2的末尾
    int tail = m + n - 1;  // 指向nums1的末尾,用于从后往前填充元素

    // 当两个数组都还有元素时,进行比较和合并
    while (p1 >= 0 && p2 >= 0) {
        // 将较大的元素填充到nums1的末尾,并移动对应的指针
        if (nums1[p1] > nums2[p2]) {
            nums1[tail] = nums1[p1];
            p1--;
        } else {
            nums1[tail] = nums2[p2];
            p2--;
        }
        tail--;
    }

    // 如果nums2还有剩余元素,直接复制到nums1的前部
    // 因为p1指针已经移动到了-1的位置,所以只需考虑p2即可
    while (p2 >= 0) {
        nums1[tail] = nums2[p2];
        p2--;
        tail--;
    }
}

第三种解法:归并排序的合并步骤

事实上,第一种解法本质上就是归并排序中的合并步骤。归并排序的核心操作是将两个已排序的数组(或子数组)合并成一个排序数组,这与我们当前的问题非常相似。

实现思路

  1. 使用两个指针p1p2,分别初始化为nums1nums2的起始位置。
  2. 使用一个额外的数组merged,保存合并后的结果。
  3. 比较p1p2所指的元素,将较小的元素放到merged中,并移动对应的指针。
  4. 当一个数组被遍历完后,将另一个数组剩余的元素全部放入merged中。
  5. 最后,将merged数组的内容复制到nums1中。

时间复杂度分析

  1. p1p2两个指针最多会遍历其对应的数组一次,所以时间复杂度为O(m+n)。

空间复杂度分析

  1. 由于使用了一个额外的merged数组来保存结果,所以空间复杂度为O(m+n)。

代码实现

public void merge(int[] nums1, int m, int[] nums2, int n) {
    int[] merged = new int[m + n]; // 创建一个新数组用于存储合并后的结果
    int p1 = 0;  // 指向nums1的起始位置
    int p2 = 0;  // 指向nums2的起始位置
    int p = 0;   // 指向merged的起始位置

    // 当两个数组都还有元素时,进行比较和合并
    while (p1 < m && p2 < n) {
        // 将较小的元素添加到merged中,并移动对应的指针
        if (nums1[p1] <= nums2[p2]) {
            merged[p] = nums1[p1];
            p1++;
        } else {
            merged[p] = nums2[p2];
            p2++;
        }
        p++;
    }

    // 如果nums1还有剩余元素,将其全部添加到merged中
    while (p1 < m) {
        merged[p] = nums1[p1];
        p1++;
        p++;
    }

    // 如果nums2还有剩余元素,将其全部添加到merged中
    while (p2 < n) {
        merged[p] = nums2[p2];
        p2++;
        p++;
    }

    // 将merged的元素复制回nums1
    for (int i = 0; i < m + n; i++) {
        nums1[i] = merged[i];
    }
}

最近很多小伙伴,让我帮忙找一套 Java 学习资料,于是我翻遍了收藏的 1024G 资料,找到一套华为工程师总结的 Java 笔记,可以说是 Java 程序员必备!

整个资料包内容专注 Java 技术,包括 Spring、Spring Boot/Cloud、Dubbo、JVM、集合、多线程、JPA、MyBatis、MySQL、大数据、Nginx、Git、Docker、GitHub、Servlet、JavaWeb、IDEA、Redis、算法、面试题等几乎覆盖了 Java 基础和进阶的方方面面,非常适合初学者入门和进阶者巩固知识!

据说已经有小伙伴通过这套资料,成功的入职了蚂蚁金服、今日头条等大厂。而且,这些资料不是扫描版的,里面的文字都可以直接复制,非常便于我们学习!

分享在这里~ 免积分直接下载~
https://download.csdn.net/download/weixin_42116348/88439145

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

宋小黑

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值