算法——寻找两个有序数组的中值

来源:互联网 发布:生鲜运输 知乎 编辑:程序博客网 时间:2024/05/17 23:20

1. 算法描述

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

2. 问题分析

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

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

/** * 合并有序数组,然后寻找K值 *  * @param a *            有序数组a * @param b *            有序数组b * @param k *            k值位置,0<=k<=a.length+b.length-1 * @return k值 */public static int findKthByMerge(int[] a, int[] b, int k) {System.out.println("Find kth by merge array first");int[] ab = new int[a.length + b.length];int ai = 0, bi = 0, abi = 0;while (ai < a.length && bi < b.length) {ab[abi++] = (a[ai] < b[bi]) ? a[ai++] : b[bi++];}while (ai < a.length) {ab[abi++] = a[ai++];}while (bi < b.length) {ab[abi++] = b[bi++];}System.out.println(Arrays.toString(ab));return ab[k];}

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

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


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

/** * 无需合并数组,利用计数机寻找K值 *  * @param a *            有序数组a * @param b *            有序数组b * @param k *            k值位置,0<=k<=a.length+b.length-1,k同时充当计数器 * @return k值 */public static int findKthByCounter(int[] a, int[] b, int k) {System.out.println("Find kth by counter");int ai = 0, bi = 0;int kth = 0; // 保存K值while (ai < a.length && bi < b.length && k >= 0) {kth = (a[ai] < b[bi]) ? a[ai++] : b[bi++];k--;}while (ai < a.length && k >= 0) {kth = a[ai++];k--;}while (bi < b.length && k >= 0) {kth = b[bi++];k--;}return kth;}

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

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


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

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


思路三:递归二分

/** * 递归二分查找K值 *  * @param a *            有序数组a * @param b *            有序数组b * @param k *            K值位置,0<=k<=m+n-1 * @param aStart *            数组a初始查找位置 * @param aEnd *            数组a结束查找位置 * @param bStart *            数组b初始查找位置 * @param bEnd *            数组b结束查找位置 * @return k值 */public static int findKth(int a[], int b[], int k, int aStart, int aEnd,int bStart, int bEnd) {int aLen = aEnd - aStart + 1;int bLen = bEnd - bStart + 1;// 递归结束条件if (aLen == 0) {return b[bStart + k];}if (bLen == 0) {return a[aStart + k];}if (k == 0) {return a[aStart] < b[bStart] ? a[aStart] : b[bStart];}// 将k按比例分配到a和b中,(k+1)=ka+kb,int ka = (k + 1) * aLen / (aLen + bLen);int kb = (k + 1) - ka;ka += aStart;kb += bStart;// 因为a和b有序,aStart-ka , bStart-kb yi// 最大值进行比较if (a[ka] > b[kb]) {k = k - (kb - bStart); // bStart - kb 这段应当排除,调整k值aEnd = ka; // 新k值可能存在于 aStart - ka bStart = kb; // 新k值可能存在于 kb - bEnd 之间} else {k = k - (ka - aStart);bEnd = kb;aStart = ka;}return findKth(a, b, k, aStart, aEnd, bStart, bEnd);}
本方法计算中值每次将范围缩小一半,故而 其 时间复杂度为 lg(m+n).


3. 测试算法

public static void main(String[] args) {int A[] = { 0, 10, 30, 40, 50, 80, 89, 99, 101 };// int A[]={};int B[] = { -1, 33, 36, 56, 80, 83, 97, 98, 200 };// int B[] = {};int k = 0;int kth = 0;k = (A.length + B.length - 1) / 2;System.out.println("A.length=" + A.length + "\t" + Arrays.toString(A));System.out.println("B.length=" + B.length + "\t" + Arrays.toString(B));System.out.println("k-index = " + k);kth = findKthByMerge(A, B, k);System.out.println(kth);kth = findKthByCounter(A, B, k);System.out.println(kth);System.out.println("递归查找");kth = findKth(A, B, k, 0, A.length - 1, 0, B.length - 1);System.out.println(kth);}

输出结果如下:

A.length=9[0, 10, 30, 40, 50, 80, 89, 99, 101]B.length=9[-1, 33, 36, 56, 80, 83, 97, 98, 200]k-index = 8Find kth by merge array first[-1, 0, 10, 30, 33, 36, 40, 50, 56, 80, 80, 83, 89, 97, 98, 99, 101, 200]56Find kth by counter56递归查找56


7 0
原创粉丝点击