笔试算法题(32):归并算法求逆序对 & 将数组元素转换为数组中剩下的其他元素的乘积
出题:多人按照从低到高排成一个前后队列,如果前面的人比后面的高就认为是一个错误对;
例如:[176,178,180,170,171]中的错误对
为
<176,170>, <176,171>, <178,170>, <178,171>,
<180,170>, <180,171>。
现在要求从一个整数序列中找出所有这样的错误对;
分析:
- 逆序对(Inversion Pair):在N个可判断大小的数中,逆序对的数量为[0,n(n-1)/2],使用归并排序求一个序列的逆序对数量,时间复杂度为O(NlogN),空 间复杂度为O(N);
- 使用m=(i+j)/2递归处理数字序列,首先计算小子文件的逆序对,并进行排序;排序之后的小子文件参与大文件的逆序对求取,由于 已经小子文件已经排序,所以可以避免许多比较操作;
解题:
1 int Partition(int *array, int i, int j) { 2 /** 3 * 使用额外O(N)的空间保存最终排序的序列 4 * */ 5 int m=(i+j)/2; 6 int tarray[j-i+1]; int index=0; 7 int ti=i,tj=m+1; 8 int count=0; 9 10 printf("\n%d,%d, %d,%d",i,j,array[i],array[j]); 11 while(ti<=m && tj<=j) { 12 if(array[ti]>array[tj]) { 13 /** 14 * 注意仅当右边序列的元素小于左边序列 15 * 的元素时,count的值才会增加,并且根据 16 * 以排序的特性可得出总计的逆序对 17 * */ 18 count+=m-ti+1; 19 tarray[index]=array[tj]; 20 tj++; 21 } else { 22 tarray[index]=array[ti]; 23 ti++; 24 } 25 index++; 26 } 27 /** 28 * 注意处理当左右子序列的剩余元素,由于已经排序,所以 29 * 可以直接复制到tarray中 30 * */ 31 if(ti>m) { 32 while(tj<=j) { 33 tarray[index]=array[tj]; 34 tj++;index++; 35 } 36 37 } else if(tj>j) { 38 while(ti<=m) { 39 tarray[index]=array[ti]; 40 ti++;index++; 41 } 42 } 43 44 for(int k=i;k<=j;k++) 45 array[k]=tarray[k]; 46 47 return count; 48 } 49 50 int Merge(int *array, int i, int j) { 51 /** 52 * 当只有一个元素的时候,返回0 53 * 当i和j相邻的时候,使用直接比较替代递归调用 54 * */ 55 printf("\n**%d, %d",i,j); 56 if(i==j) return 0; 57 if(i+1==j) { 58 if(array[i]>array[j]) { 59 int t=array[i]; 60 array[i]=array[j]; 61 array[j]=t; 62 return 1; 63 } else 64 return 0; 65 } 66 67 /** 68 * 使用二分递归,count的值由三部分决定: 69 * 左右子序列各自内部的逆序对,和左子序列和 70 * 右子序列之间的逆序对。 71 * 由于经过Merge之后左右子序列已经排序,所以 72 * partition可以在O(N)时间复杂度内完成,但是 73 * 需要额外的O(N)的空间复杂度 74 * */ 75 int m=(i+j)/2; 76 int count=0; 77 count+=Merge(array,i,m); 78 count+=Merge(array,m+1,j); 79 count+=Partition(array,i,j); 80 81 return count; 82 } 83 84 int main() { 85 int array[]={7,2,1,4,3,5,6}; 86 printf("\n%d",Merge(array, 0, 6)); 87 88 return 0; 89 }
出题:一个长度为n的数组a[0],a[1],...,a[n-1]。现在更新数组的名个元素,即a[0]变为a[1]到a[n-1]的积,a[1]变为 a[0]和a[2]到a[n-1]的积,...,a[n-1]为a[0]到a[n-2]的积(就是除掉当前元素,其他所有元素的积);
1). 要求具有线性复杂度;
2). 要求不能使用除法运算;
分析:创建两个数组left[N]和right[N],对于a[i]而言,left[i]存储i之前的元素乘积,right[i]存储i之后的元素乘积,所以left和right的初始化仅需要两次扫描数组,为线性时间复杂度,并且没有使用除法;
解题:
1 void Transfer(int *array, int length) { 2 int leftarray[length]; 3 int rightarray[length]; 4 5 leftarray[0]=1; 6 for(int i=1;i<length;i++) 7 leftarray[i]=leftarray[i-1]*array[i-1]; 8 9 rightarray[length-1]=1; 10 for(int i=length-2;i>-1;i--) 11 rightarray[i]=rightarray[i+1]*array[i+1]; 12 13 for(int i=0;i<length;i++) 14 array[i]=leftarray[i]*rightarray[i]; 15 } 16 17 int main() { 18 int array[]={5,2,3,4}; 19 int length=4; 20 Transfer(array,length); 21 for(int i=0;i<length;i++) 22 printf("%d, ",array[i]); 23 return 0; 24 }