ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

【Leetcode数据结构算法题】合并两个有序数组(顺序表篇)

2022-01-01 18:03:18  阅读:171  来源: 互联网

标签:arr num1 int nums1 Leetcode src1 数组 数据结构 表篇


题目内容:

给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2中的元素数目。 请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。
注意:最终合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。

在这里插入图片描述
在这里插入图片描述

leetcode题目链接(点击即可跳转):


思路分析

看完题目,我们先别火急火燎的去编程做题,而是要先仔细理解题目的意思。(某些同学这时候可能会说“不是吧,不是吧,题目内容都给出来了,不会还有人看不懂吧?不会吧,不会吧!”)实际上,可能很多小伙伴们刚上手OJ题时(online judge 在线编程题)都不会很认真去审题,而是直接编程,然后报出一大堆错误,一个个调试,最后。。。就没有最后了,真的是“从入门到放弃~”。因此,为了避免这个问题,更加高效和正确的练习,我们应该要“理解题目—思路分析(画图理解)—极端场景—动手编程—回顾总结”。这些步骤看起来好像挺繁琐的,但是实际上花的时间比直接上手编程还要少,而且最终的学习、练习效果也要好上不少。

(当然,分享博客也可以帮助我们梳理做题的思路,总结做题的经验和方法,最最最重要的是能帮助到其他人,然刷题的这个过程多了一些“其他的意义”)


那么问题来了,如何更好的理解题目呢?
(1)将题目看完后,用自己的话复述出来,只需要大概的意思相同即可。

比如说这道题目,题目大概的意思就是“有两个整体而言升序(就是中间可能存在相等的元素,不是严格升序,这就是对”非递减排列“的理解)的数组num1,num2。num1有m个元素,但是长度为m+n,num2有n个元素,长度为n。现在要求我们将两个数组合并成一个数组并存放到num1当中,排序方式跟之前一样。”

(2)从多个角度给自己提问

比如说:
①题目有要求说不能开辟临时数组吗?或者说要求空间复杂度为O(1),又或者是原地处理数据。(显然本题是没有这个要求的!)
②题目中有说m、n不能等于0吗?如果等于0时该怎么处理?
③题目中对时间复杂度有要求吗?(进阶部分要求时间复杂度为O(n+m)

方法一:临时数组法

既然题目中没有要求“原地”合并数据,那么我们应该很容易想到建立一个长度为n+m的临时数组,先将num1,num2两个数组中的内容按照“不降序”方式放到临时数组中,然后将临时数组中的内容拷贝回num1数组中。
这种方式对于题目②出现的情况:

1)m=0、n=0 不符合题目提示中 1 <=
m+n的情况。(其实我还有点好奇如果真遇到了,m=n=0,m+n=0,怎么处理,能创建长度为0的临时数组吗?长度为0的数组有意义吗?m=0时只是代表num1中没有有效元素,但是实际的长度不为m,而是n+m >=1。如果真遇到了,都不用开辟临时数组,直接处理即可)
2)m=0、n>0,也就是说num1中无有效元素,num2中有n个有效元素,这个时候就会将num2中的数据先全部放到临时数组中,然后从数组中拷贝到num1中,可以解决问题,不会出现错误(虽然效率有点低,但好歹解决问题了,问题不大)
3)n=0、m>0,也就是说num2中无有效元素,num1中有m个有效元素,这个时候就会将num1中的数据先全部放到临时数组中,然后从数组中拷贝到num1中,可以解决问题,不会出现错误。(额,看起来呆呆的)

所以整体来看,临时数组的方式不会出现太大的问题,这个时候就可以画出图解,编写代码测试了。


算法图解

在这里插入图片描述


函数接口实现

void merge(int* nums1, int nums1Size, int m, 
int* nums2, int nums2Size, int n){
    int* arr = (int*)malloc((m+n)*sizeof(int));//创建长度为m+n的临时变量
    int src1 = 0;//nums1的起始下标
    int src2 = 0;//nums2的起始下标
    int dest = 0;//arr的起始下标
    while((src1 < m) && (src2 < n)){
        if(nums1[src1] <= nums2[src2]){
            //将nums1的元素赋给arr,两个数组下标同时向后移动
            arr[dest++] = nums1[src1++];
        }
        else{
            //将nums2的元素赋给arr,两个数组下标同时向后移动
            arr[dest++] = nums2[src2++];
        }
    }
    //走到这里,表示num1或num2有一个已经结束了
    if(src1 == m){
        //src1 = m表示num1中的有效元素全部移到arr中了
        //只需要将num2中的元素全部移到arr中即可
        while(src2 < n){
            arr[dest++] = nums2[src2++];
        }
    }
    else{
        //src2 = n表示num2中的有效元素全部移到arr中了
        //只需要将num1中的元素全部移到arr中即可
        while(src1 < m){
            arr[dest++] = nums1[src1++];
        }
    }
    //最后将arr中的元素拷贝给num1
    for(int i = 0; i < (n+m); i++){
        nums1[i] = arr[i];
    }
    //释放动态开辟的数组
    free(arr);
    arr = NULL;
}

在这里插入图片描述
上述的这种使用临时数组的方法,我们再将num1、num2中的元素移到临时数组时,采用了从num1、num2两个数组的起始端到终止端的方式,也就是说采用了“从前往后”的方式。那么我们可不可以采用”从后往前“的方式呢?(既然我都问了,那我自然就不能”光说不做假把戏“)


方法二:临时数组倒立版

(别问为什么叫倒立版,为什么不叫翻转版、逆序版、反向版???问就是我感觉”倒立版”三个字比较“霸气”,对,就是比较“霸气”,有木有一种“霸气外露”的感觉?)
这种方法只需要在原来临时数组法的基础上进行一些调整即可,主要是将src1、src2两个下标从之前的“从前往后”遍历变成“从后往前”遍历,那么两者比较的时候,就是谁大,谁就保留到临时数组arr中,注意临时数组也需要从后往前遍历。

算法图解

在这里插入图片描述

函数接口实现

void merge(int* nums1, int nums1Size, int m, 
int* nums2, int nums2Size, int n){
    int* arr = (int*)malloc((m+n)*sizeof(int));//创建长度为m+n的临时变量
    int src1 = m - 1;//nums1的终止下标
    int src2 = n - 1;//nums2的终止下标
    int dest = m + n - 1;//arr的终止下标
    while((src1 >= 0) && (src2 >= 0)){
        if(nums1[src1] >= nums2[src2]){
            //将nums1的元素赋给arr,两个数组下标同时向前移动
            arr[dest--] = nums1[src1--];
        }
        else{
            //将nums2的元素赋给arr,两个数组下标同时向前移动
            arr[dest--] = nums2[src2--];
        }
    }
    //走到这里,表示num1或num2有一个已经结束了
    if(src1 < 0){
        //src1 < 0表示num1中的有效元素全部移到arr中了
        //只需要将num2中的元素全部移到arr中即可
        while(src2 >= 0){
            arr[dest--] = nums2[src2--];
        }
    }
    else{
        //src2 < 0表示num2中的有效元素全部移到arr中了
        //只需要将num1中的元素全部移到arr中即可
        while(src1 >= 0){
            arr[dest--] = nums1[src1--];
        }
    }
    //最后将arr中的元素拷贝给num1
    for(int i = 0; i < (n+m); i++){
        nums1[i] = arr[i];
    }
    //释放动态开辟的数组
    free(arr);
    arr = NULL;
}

在这里插入图片描述


使用临时数组的方法,虽然可以解决问题,但是我们假设出现这样一种情况“需要合并的两个数组很大,也就是m+n很大”,那么我们需要创建的临时数组的空间就越大,空间复杂度为O(n+m)。上面的两种临时数组方法时间复杂度都是O(n+m),也就是说符合进阶版的要求。
时间复杂度、空间复杂度相关文章:
【数据结构学习笔记】一、数据结构介绍及算法分析(新手入门进阶指南)
那么有没有一种方法既可以不开辟临时数组,又可以达到类似临时数组的效果呢?(也就是说时间复杂度不变,为O(n+m),但是空间复杂度变成了O(1))
答案当然是“有!”,这种方法我愿称之为“超级进阶版,豪华进阶版,终极plus版”,至少目前我还找不到比这种方法还高效的算法了。

方法三:虚拟数组法(超级豪华终极puls版)

这种方法可以说是从“临时数组倒立版”发展而来(至少我是这么想到的),倒立版中两个数组从后往前遍历将较大的数保存到临时数组中。实际上可以不用放到临时数组里面,而是可以直接放到num1里面。因为num1本身的长度是够了(m+n)且num1后面的n个位置是不放有效数据的,不用担心数据被覆盖掉。(如果你要从前往后遍历就需要担心将原来num1中的数据给覆盖了)最后将临时数组内容拷贝回num1中的这一步可以省略。(此处应该有“666、Nb、我滴天啦!”)


算法图解

在这里插入图片描述


函数接口实现

void merge(int* nums1, int nums1Size, int m, 
int* nums2, int nums2Size, int n){
    int src1 = m - 1;//数组nums1有效元素的终止下标
    int src2 = n - 1;//数组nums2的终止下标
    int dest = m + n - 1;//数组nums1整体的终止下标
    while(src1 >= 0 && src2 >=0){
        if(nums1[src1] >= nums2[src2]){
            //将nums1的src1指向的元素赋给nums1的dest指向的位置
            //两个数组下标同时向前移动
            nums1[dest--] = nums1[src1--];
        }
        else{
            //将nums2的src2指向的元素赋给nums1的dest指向的位置
            //两个数组下标同时向前移动
            nums1[dest--] = nums2[src2--];
        }
    }
    //走到这里代表src1或src2走完了
    if(src1 < 0){
        //如果是src1提前走完,将nums2中元素全部拷贝给nums1
        while(src2 >= 0){
            nums1[dest--] = nums2[src2--];
        }
    }
    //如果是src2提前走完,不需要进行任何处理
}

在这里插入图片描述


原创不易,各位小伙伴点个赞,评个论,光个注呗~
你们的支持将是我前进的最大动力!!!

标签:arr,num1,int,nums1,Leetcode,src1,数组,数据结构,表篇
来源: https://blog.csdn.net/QIYICat/article/details/122258005

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有