LeetCode 力扣 88. 合并两个有序数组

题目描述(简单难度)

给两个有序数组,把第二个数组合并到第一个数组中,保持有序。可以注意到第一个数组已经为我们多开辟了第二个数组所需要的空间。

解法一 直接法

简单粗暴,nums1 作为被插入的数组,然后遍历 nums2。用两个指针 i 和 j ,i 指向 nums1 当前判断的数字,j 指向 num2 当前遍历的数字。如果 j 指向的数字小于 i 指向的数字,那么就做插入操作。否则的话后移 i ,找到需要插入的位置 。

1 2 3 0 0 0  | 2 5 6  //当前 j 指向的数字不小于 i 指向的数字,无需插入,后移 i
^              ^
i              j

1 2 3 0 0 0  | 2 5 6  //当前 j 指向的数字不小于 i 指向的数字,无需插入后移 i
  ^            ^
  i            j

1 2 3 0 0 0  | 2 5 6  //当前 j 指向的数字小于 i 指向的数字,将当前数字插入到 nums1 中
    ^          ^
    i          j
    
1 2 2 3 0 0  | 2 5 6  //插入完成后,j 进行后移,同时由于在 i 前边插入了数字,i 后移回到原来的数字
    ^          ^
    i          j 
    
1 2 2 3 0 0  | 2 5 6  //当前 j 指向的数字不小于 i 指向的数字,无需插入后移 i
      ^          ^
      i          j 

1 2 2 3 0 0  | 2 5 6  //由于 nums1 完成遍历,将剩余的 nums2 直接插入
        ^        ^
        i        j 
        
1 2 2 3 5 6  | 2 5 6  
          ^        ^
          i        j 
public void merge(int[] nums1, int m, int[] nums2, int n) {
    int i = 0, j = 0;
    //遍历 nums2
    while (j < n) {
        //判断 nums1 是否遍历完
        //(nums1 原有的数和当前已经插入的数相加)和 i 进行比较
        if (i == m + j) {
            //将剩余的 nums2 插入
            while (j < n) {
                nums1[m + j] = nums2[j];
                j++;
            }
            return;
        }
        //判断当前 nums2 是否小于 nums1
        if (nums2[j] < nums1[i]) {
            //nums1 后移数组,空出位置以便插入
            for (int k = m + j; k > i; k--) {
                nums1[k] = nums1[k - 1];
            }
            nums1[i] = nums2[j];
            //i 和 j 后移
            j++;
            i++;
        //当前 nums2 不小于 nums1, i 后移
        }else{ 
            i++;  
        }
    }
}

时间复杂度:极端情况下,如果每次都需要插入,那么是 O(n²)。

空间复杂度:O(1)。

解法二

两个有序数组的合并,其实就是归并排序中的一个步骤。回想下,我们当时怎么做的。

我们当时是新开辟一个和 nums1 + nums2 等大的空间,然后用两个指针遍历 nums1 和 nums2,依次选择小的把它放到新的数组中。

这道题中,nums1 其实就是我们最后合并好的大数组,但是如果 nums1 当作上述新开辟的空间,那原来的 nums1 的数字放到哪里呢?放到 nums1 的末尾。这样我们就可以完全按照归并排序中的思路了,用三个指针就可以了。

1 2 3 0 0 0 0 | 2 5 6 7 //将 nums1 的数据放到 nums1 的末尾
   
1 2 3 0 1 2 3 | 2 5 6 7 //i 和 j 分别指向两组数据的开头,k 指向代插入位置
^       ^       ^           
k       i       j

1 2 3 0 1 2 3 | 2 5 6 7 //此时 i 指向的数据小,把它插入,然后 i 后移,k 后移
^       ^       ^           
k       i       j

1 2 3 0 1 2 3 | 2 5 6 7 //此时 i 指向的数据小,把它插入,然后 i 后移,k 后移
  ^       ^     ^           
  k       i     j
  
1 2 3 0 1 2 3 | 2 5 6 7 //此时 j 指向的数据小,把它插入,然后 j 后移,k 后移
    ^       ^   ^           
    k       i   j
    
1 2 2 0 1 2 3 | 2 5 6 7 //此时 i 指向的数据小,把它插入,然后 i 后移,k 后移
      ^     ^     ^             
      k     i     j
      
1 2 2 3 1 2 3   | 2 5 6 7 //此时 i 遍历完,把 nums2 全部加入
        ^     ^     ^           
        k     i     j
        
1 2 2 3 5 6 7   | 2 5 6 7 
      
public void merge(int[] nums1, int m, int[] nums2, int n) {
    //将 nums1 的数字全部移动到末尾
    for (int count = 1; count <= m; count++) {
        nums1[m + n - count] = nums1[m - count];
    }
    int i = n; //i 从 n 开始
    int j = 0; 
    int k = 0;
    //遍历 nums2
    while (j < n) {
        //如果 nums1 遍历结束,将 nums2 直接加入
        if (i == m + n) {
            while (j < n) {
                nums1[k++] = nums2[j++];
            }
            return;
        }
        //哪个数小就对应的添加哪个数
        if (nums2[j] < nums1[i]) {
            nums1[k] = nums2[j++];
        } else {
            nums1[k] = nums1[i++];
        }
        k++;
    }
}

时间复杂度: O(n)。

空间复杂度:O(1)。

可以注意到,我们只考虑如果 nums1 遍历结束,将 nums2 直接加入。为什么不考虑如果 nums2 遍历结束,将 nums1 直接加入呢?因为我们最开始的时候已经把 nums1 全部放到了末尾,所以不需要再赋值了。

解法三

本以为自己的解法二已经很机智了,直到看到了这里,发现了一个神仙操作。

解法二中我们的思路是,把 nums1 当作合并后的大数组,依次从两个序列中选较小的数,此外,为了防止 nums1 原有的数字被覆盖,首先先把他放到了末尾。

那么,我们为什么不从 nums1 的末尾开始,依次选两个序列末尾较大的数插入呢?同样是 3 个指针,只不过变成哪个数大就对应的添加哪个数。

public void merge3(int[] nums1, int m, int[] nums2, int n) {
    int i = m - 1; //从末尾开始
    int j = n - 1; //从末尾开始
    int k = m + n - 1; //从末尾开始
    while (j >= 0) {
        if (i < 0) {
            while (j >= 0) {
                nums1[k--] = nums2[j--];
            }
            return;
        }
        //哪个数大就对应的添加哪个数。
        if (nums1[i] > nums2[j]) {
            nums1[k--] = nums1[i--];
        } else {
            nums1[k--] = nums2[j--];
        }
    }
}

时间复杂度: O(n)。

空间复杂度:O(1)。

这道题看起来简单,但用到的思想很经典了。解法二中充分利用已有空间的思想,以及解法三中逆转我们的惯性思维,给定的数组从小到大,然后惯性上习惯从小到大,但如果逆转过来,从大的选,简直是神仙操作了!

更多详细通俗题解详见 leetcode.wang

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容