寻找两个有序数组的中值

1. 算法描述

有两个数组 A 和 B,均为有序排列,A的长度为m,B的长度为n,求 A 和 B 合在一起后的中值.

2. 问题分析

  • 这里要注意一下:要充分利用 A和B均为有序的特性
  • 该问题进一步可转化为求A和B的任意K值,如三分位、四分位.

思路一:将A和B合并成新的数组

[java]  view plain  copy
  1. /** 
  2.      * 合并有序数组,然后寻找K值 
  3.      *  
  4.      * @param a 
  5.      *            有序数组a 
  6.      * @param b 
  7.      *            有序数组b 
  8.      * @param k 
  9.      *            k值位置,0<=k<=a.length+b.length-1 
  10.      * @return k值 
  11.      */  
  12.     public static int findKthByMerge(int[] a, int[] b, int k) {  
  13.         System.out.println("Find kth by merge array first");  
  14.         int[] ab = new int[a.length + b.length];  
  15.         int ai = 0, bi = 0, abi = 0;  
  16.         while (ai < a.length && bi < b.length) {  
  17.             ab[abi++] = (a[ai] < b[bi]) ? a[ai++] : b[bi++];  
  18.         }  
  19.         while (ai < a.length) {  
  20.             ab[abi++] = a[ai++];  
  21.         }  
  22.         while (bi < b.length) {  
  23.             ab[abi++] = b[bi++];  
  24.         }  
  25.         System.out.println(Arrays.toString(ab));  
  26.   
  27.         return ab[k];  
  28.     }  

这种方法最容易想到,合并成有序数组后即可求任意k值, 其时间复杂度为 O(m+n), 空间复杂图为O(m+n)

这里反思一下:真的需要合并数组吗?


思路二:采用扫描计数方法

[java]  view plain  copy
  1. /** 
  2.      * 无需合并数组,利用计数机寻找K值 
  3.      *  
  4.      * @param a 
  5.      *            有序数组a 
  6.      * @param b 
  7.      *            有序数组b 
  8.      * @param k 
  9.      *            k值位置,0<=k<=a.length+b.length-1,k同时充当计数器 
  10.      * @return k值 
  11.      */  
  12.     public static int findKthByCounter(int[] a, int[] b, int k) {  
  13.         System.out.println("Find kth by counter");  
  14.         int ai = 0, bi = 0;  
  15.         int kth = 0// 保存K值  
  16.         while (ai < a.length && bi < b.length && k >= 0) {  
  17.             kth = (a[ai] < b[bi]) ? a[ai++] : b[bi++];  
  18.             k--;  
  19.         }  
  20.         while (ai < a.length && k >= 0) {  
  21.             kth = a[ai++];  
  22.             k--;  
  23.         }  
  24.         while (bi < b.length && k >= 0) {  
  25.             kth = b[bi++];  
  26.             k--;  
  27.         }  
  28.         return kth;  
  29.     }  

本算法是对算法一的改进,用一个临时变量保存K值,而不需要讲新合并的数组单独存储,节省了存储空间。

其 时间复杂度为O(m+n), 空间复杂度为O(1).


到此都是线性时间复杂度,已经是非常高效了,但又没有更加高效的方法进一步降低时间复杂度呢?

这里注意到原数组有序特性,利用二分特性可以将复杂度降至对数级别。


思路三:递归二分

[java]  view plain  copy
  1. /** 
  2.      * 递归二分查找K值 
  3.      *  
  4.      * @param a 
  5.      *            有序数组a 
  6.      * @param b 
  7.      *            有序数组b 
  8.      * @param k 
  9.      *            K值位置,0<=k<=m+n-1 
  10.      * @param aStart 
  11.      *            数组a初始查找位置 
  12.      * @param aEnd 
  13.      *            数组a结束查找位置 
  14.      * @param bStart 
  15.      *            数组b初始查找位置 
  16.      * @param bEnd 
  17.      *            数组b结束查找位置 
  18.      * @return k值 
  19.      */  
  20.     public static int findKth(int a[], int b[], int k, int aStart, int aEnd,  
  21.             int bStart, int bEnd) {  
  22.   
  23.         int aLen = aEnd - aStart + 1;  
  24.         int bLen = bEnd - bStart + 1;  
  25.   
  26.         // 递归结束条件  
  27.         if (aLen == 0) {  
  28.             return b[bStart + k];  
  29.         }  
  30.         if (bLen == 0) {  
  31.             return a[aStart + k];  
  32.         }  
  33.         if (k == 0) {  
  34.             return a[aStart] < b[bStart] ? a[aStart] : b[bStart];  
  35.         }  
  36.   
  37.         // 将k按比例分配到a和b中,(k+1)=ka+kb,  
  38.         int ka = (k + 1) * aLen / (aLen + bLen);  
  39.         int kb = (k + 1) - ka;  
  40.         ka += aStart;  
  41.         kb += bStart;  
  42.   
  43.         // 因为a和b有序,aStart-ka , bStart-kb yi  
  44. <span style="white-space:pre;">     </span>// 最大值进行比较         
  45.         if (a[ka] > b[kb]) {  
  46.             k = k - (kb - bStart); // bStart - kb 这段应当排除,调整k值  
  47.             aEnd = ka; // 新k值可能存在于 aStart - ka   
  48.             bStart = kb; // 新k值可能存在于 kb - bEnd 之间  
  49.         } else {  
  50.             k = k - (ka - aStart);  
  51.             bEnd = kb;  
  52.             aStart = ka;  
  53.         }  
  54.         return findKth(a, b, k, aStart, aEnd, bStart, bEnd);  
  55.     }  
本方法计算中值每次将范围缩小一半,故而 其  时间复杂度为 lg(m+n).


3. 测试算法

[java]  view plain  copy
  1. public static void main(String[] args) {  
  2.         int A[] = { 010304050808999101 };  
  3.         // int A[]={};  
  4.         int B[] = { -133365680839798200 };  
  5.         // int B[] = {};  
  6.         int k = 0;  
  7.         int kth = 0;  
  8.   
  9.         k = (A.length + B.length - 1) / 2;  
  10.         System.out.println("A.length=" + A.length + "\t" + Arrays.toString(A));  
  11.         System.out.println("B.length=" + B.length + "\t" + Arrays.toString(B));  
  12.         System.out.println("k-index = " + k);  
  13.   
  14.         kth = findKthByMerge(A, B, k);  
  15.         System.out.println(kth);  
  16.   
  17.         kth = findKthByCounter(A, B, k);  
  18.         System.out.println(kth);  
  19.   
  20.         System.out.println("递归查找");  
  21.         kth = findKth(A, B, k, 0, A.length - 10, B.length - 1);  
  22.         System.out.println(kth);  
  23.     }  

输出结果如下:

[plain]  view plain  copy
  1. A.length=9  [0, 10, 30, 40, 50, 80, 89, 99, 101]  
  2. B.length=9  [-1, 33, 36, 56, 80, 83, 97, 98, 200]  
  3. k-index = 8  
  4. Find kth by merge array first  
  5. [-1, 0, 10, 30, 33, 36, 40, 50, 56, 80, 80, 83, 89, 97, 98, 99, 101, 200]  
  6. 56  
  7. Find kth by counter  
  8. 56  
  9. 递归查找  
  10. 56  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值