Problem Description:
Describe an O(n)-time algorithm that, given a set of S of n distinct numbers and a positive integerd k ≤ n,
determines the k numbers in S that are closest to the median of S.
问题描述:
有一个长为n的数组且数组元素互不相同,要求以O(n)的时间复杂度找到离数组中值最近(与中值的差的绝对值最小)的k个数(k ≤ n)。
问题升级:
题目要求是找离中值最近的 k 个数,这里做一下推广,找离数组中第 i 个数最近的k个数。原题作为 i = n / 2 + n % 2; 时的特殊情况。
解决方案:
1、首先,找数组的第 i 小的数是O(n)的(参见 ithSmallestLinear )。
2、假设数组中第 i - k 小的数是 m ,第 i + k 小的数是 n ,则离数组第 i 个数最近的 k 个数一定在[m, n]这个大小范围内。
3、把原数组中上述范围内的数放到数组最前面,然后求这些数离第 i 个数的距离,并找到第 k 小的距离 distThreshold。
4、用这个阈值再扫描一遍原数组即可得到离第 i 个数最近的 k 个数。
需要注意的是:
由于原数组的元素是互不相同的,则满足离第 i 个数距离为distThreshold的数的个数只可能为 k 或者 k + 1。
因为存在这样一种情况:2 5 8 中 2 和 8 离 5 的距离相同都为 3,但不可能在原数组中找到第三个数离 5 的距离为 3。
因此在第四步扫描原数组找最近的 k 个数的时候要处理一下这种情况。
实现代码:
1 View Code 2 void selectKClosestNumbers(int a[], int beg, int end, int i, int k) 3 { 4 if (k > end - beg) 5 { 6 return; 7 } 8 9 int ivalue = a[ithSmallestLinear(a, beg, end, i)]; 10 int lowerBoundSeqNum = (i - k < 1) ? 1 : i - k; 11 int upperBoundSeqNum = (i + k > end - beg + 1) ? end - beg + 1 : i + k; 12 int lowerBound = a[ithSmallestLinear(a, beg, end, lowerBoundSeqNum)]; 13 int upperBound = a[ithSmallestLinear(a, beg, end, upperBoundSeqNum)]; 14 15 int count = 0; 16 //search elements in the range of [lowerbound, upperbound], at most 2k 17 for (int j = 0; (count != 2 * k) && (j != end); ++j) 18 { 19 if ((a[j] >= lowerBound) && (a[j] <= upperBound) && (a[j] != ivalue)) 20 { 21 swap(a, j, count++); 22 } 23 } 24 int *dist = new int[count]; 25 //calculate the distance to the ivalue 26 for (int j = 0; j != count; ++j) 27 { 28 dist[j] = abs(a[j] - ivalue); 29 } 30 int threshold = dist[ithSmallestLinear(dist, 0, count - 1, k)]; 31 //the number of candidate whose distance to the ivalue <= threshold 32 //candidateNum == k or k+1 33 int candidateNum = 0; 34 for (int j = 0; j != count; ++j) 35 { 36 if (dist[j] <= threshold) 37 { 38 ++candidateNum; 39 } 40 } 41 42 //select k elements 43 for (int j = 0, t = 0; t != k; ++j) 44 { 45 int tmpDist = abs(a[j] - ivalue); 46 if (tmpDist < threshold) 47 { 48 swap(a, j, t++); 49 } 50 //select the second candidate once there are two candidiates 51 //whose distance to the ivalue equal threshold 52 if ((tmpDist == threshold) && (candidateNum-- != k + 1)) 53 { 54 swap(a, j, t++); 55 } 56 } 57 58 delete [] dist; 59 }
测试:
取数组元素为 1 2 3 4 5 6 7 8 9 10
①找与第6个数(6)最近的5个数,这里dist的阈值是3,而4和9同时满足这个阈值,按照实现代码里的逻辑,我们取排在后面的9。
②找与第1个数(1)最近的3个数,这种情况下没有比1小的数,所以这3个数应该是2 3 4。
测试代码:
1 int main(void) 2 { 3 int testArrayA[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 4 int testArrayB[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 5 6 selectKClosestNumbers(testArrayA, 0, 9, 6, 5); 7 selectKClosestNumbers(testArrayB, 0, 9, 1, 3); 8 9 outputArray(std::cout, testArrayA, 0, 4); 10 outputArray(std::cout, testArrayB, 0, 2); 11 12 return 0; 13 }
文中一些自定义函数的实现见文章“#include”