@TOC

一、题目描述

NC22合并两个有序的数组
原题链接https://www.nowcoder.com/practice/89865d4375634fc484f3a24b7fe65665?tpId=117
题目大意:给定递增数组A,B,合并A,B的元素,使合并过后的数组是递增的,并且存放在数组A中
注意审题:题目中有一句话:可以假设A数组有足够的空间存放 B数组的元素,即无需考虑溢出问题,或理解为A数组空间足够

二、算法1

算法思路

  1. 总体思想:归并排序思想
  2. 该算法空间复杂度为,时间复杂度
  3. 开辟一个新数组C,定义指针i,j,p,其中三个指针分别指向A,B,C的开头,即i=j=p=0(注意此处的指针并不是C语言中的指针,此处代表指向一个位置的int变量)
  4. 比较i,j所指向的元素,如果A[i]<=B[j],则将A[i]放入C中,否则将B[j]放入C中,即C[p++]=A[i]<=B[j]?A[i++]:B[j++],直到有一个数组为空,或者两个同时为空
  5. 接着再将有剩余的那个数组的元素转移到C中即可
  6. 因为题目要求,最后将C数组拷贝到A数组中

代码实现

class Solution {
public:
    void merge(int A[], int m, int B[], int n) {
          int i=0,j=0,p=0; 
        int c[m+n];//定义辅助数组C 
        while(i<m&&j<n){
            c[p++]=A[i]<=B[j]?A[i++]:B[j++];//将A[i]和B[j]中小的那个丢入C中 
        }      
        while(i<m){//如果A中有剩余 
            c[p++]=A[i++];
        }
        while(j<n){//如果B中有剩余 
            c[p++]=B[j++];
        }
        for(int i=0;i<p;i++)A[i]=c[i];//因为题目要求,将C数组拷贝到A数组中 
    }
};

三、算法2

算法思路

  1. 较上一个算法,因为A的空间充足,所以我们可以直接在A中进行操作
  2. 定义指针i,j,p,(此处指针意义同上),i,j指针指向原末尾,p指针指向合并后的A末尾 ,即i=m-1,j=n-1,p=m+n-1
  3. 整体思路:比较A[i]和B[j],其中大的,丢到A[p]中,然后p和该指针左移一格。空间复杂度为,时间复杂度,较上一个算法空间复杂度得到了优化。
  4. 这个时候,就会有同学好奇,会不会出现A[x]还没有填写,但是被覆盖的情况,最后造成A[x]丢失,以至于答案错误的情况?,答案是显然的,既然算法是正确的,那么这种情况就必然不可能出现,下面简单证明一下。
  5. 我们反过来思考,出现上面情况的原因是什么,什么时候会出现?显然是p<i时候,才会造成一个没填入,但是被覆盖的情况
  6. 证明:因为A原本是递增的,所以该情况,p移动的比i多,j指针最多导致p移动n次,此时p=m,而i指针和p指针同时移动时候不会改变p,i之间大小的关系,所以永远有p>=i
  7. 既然算法正确,那么代码也不难理解,跟算法1很相似while(~i&&~j)A[p--]=A[i]>B[j]?A[i--]:B[j--];
  8. 此处~i等价i>=0,因为-1取反过后返回的是0,所以~i的意思就是i!=-1,因为此处是从正轴向左走,所以i!=-1等价i>=0,位运算一般情况比其他运算符速度要快,这也是一种小小优化程序的方法
  9. 最后处理A,B剩余情况,B数组剩余,显然应该将B剩下的丢入A中,但是A剩余我们还需要操作吗?
  10. 答案是不用的,因为此时有'p==i',剩下的一模一样,我们就不需要转移了(此处如果没有看懂的同学,可以看代码中的注释,此处结合代码讲解比较容易理解)

    动画演示

    图片说明

    代码实现

    C++代码

    class Solution {
    public:
        void merge(int A[], int m, int B[], int n) {
            int i=m-1,j=n-1,p=m+n-1;//i,j指针指向原末尾,p指针指向合并后的A末尾 
            while(~i&&~j) {// ~i  <==> i>=0,因为-1取反过后返回的是0,位运算一般情况比其他运算符速度要快 
                A[p--]=A[i]>B[j]?A[i--]:B[j--];//合并A、B数组, 比较i,j指针指向的元素,将大的那个丢进去 
            }
            while(~j){//如果B数组还有剩余,则直接全部移到A数组中 
                A[p--]=B[j--];
            }
            /*此处如果出现A数组有剩余的情况,理论上应该执行while(i>=0)A[p--]=A[i--]
            但是如果A有剩余必然有p==i(出现剩余的情况,只有可能一个出现剩余,不可能出现AB数组都剩余的情况) 
            (如果出现AB都剩余,就跟第一个for循环矛盾了) 
            既然对于指针p==i,那么此时这个while循环就会显得很多余                     
            */    
        }
    };

Python代码

class Solution:
    def merge(self ,A,m,B,n):
        i=m-1
        j=n-1
        p=m+n-1 #i,j指针指向原末尾,p指针指向合并后的A末尾 
        while i>=0 and j>=0 :# 合并A、B数组, 比较i,j指针指向的元素,将大的那个丢进去 
            if A[i]>B[j]:
                A[p]=A[i]
                p-=1
                i-=1
            else :
                A[p]=B[j]
                p-=1
                j-=1
        while j>=0 :# 如果B数组还有剩余,则直接全部移到A数组中 
            A[p]=B[j]
            p-=1
            j-=1