题目地址:
https://www.lintcode.com/problem/find-k-closest-elements/description
给定一个升序数组 A A A,再给定一个目标数 t t t,和一个非负整数 k k k,找离 t t t最近的 k k k个整数。需要按照与 t t t的接近程度从小到大排序。如果一样接近,则小的排在前面。
首先,离 t t t最近的 k k k个数一定在 A A A中位置连续(反证法可证),我们可以对这 k k k个数的左端点(下称合法区间左端点)位置进行二分。设左端点一开始的范围是 [ l , r ] [l,r] [l,r],显然 l = 0 , r = l A − k l=0, r=l_A-k l=0,r=lA−k。取范围中点 m m m,考虑 A [ m ] A[m] A[m]和 A [ m + k ] A[m+k] A[m+k]这两个数字(当然如果 A [ m + k ] A[m+k] A[m+k]不存在,那直接向左找,即令 r = m r=m r=m,然而在代码里却不用考虑这个问题,因为我们取的是靠左的中点,也就是说 m m m不会取到 l A − k l_A-k lA−k),如果 A [ m ] A[m] A[m]比 A [ m + k ] A[m+k] A[m+k]离 t t t更近或者一样近,那么合法区间左端点的位置应该在 [ l , m ] [l,m] [l,m];否则合法区间左端点应该在 [ m + 1 , r ] [m+1,r] [m+1,r]。找到合法区间左端点后,哪 k k k个数就确定了,可以用双指针算法将它们按照谁离的远(一样远就比谁更大)加入最后答案,最后再逆序一下即可。代码如下:
public class Solution {
/**
* @param A: an integer array
* @param target: An integer
* @param k: An integer
* @return: an integer array
*/
public int[] kClosestNumbers(int[] A, int target, int k) {
// write your code here
int[] res = new int[k];
if (k == 0) {
return res;
}
int l = 0, r = A.length - k;
while (l < r) {
int m = l + (r - l >> 1);
int left = A[m], right = A[m + k];
if (Math.abs(left - target) <= Math.abs(right - target)) {
r = m;
} else {
l = m + 1;
}
}
// 最后用双指针算法将这k个数加入res
r = l + k - 1;
int idx = 0;
while (l <= r) {
if (Math.abs(A[l] - target) <= Math.abs(A[r] - target)) {
res[idx++] = A[r--];
} else {
res[idx++] = A[l++];
}
}
// 最后反个序
reverse(res);
return res;
}
private void reverse(int[] A) {
for (int i = 0, j = A.length - 1; i < j; i++, j--) {
swap(A, i, j);
}
}
private void swap(int[] A, int i, int j) {
int tmp = A[i];
A[i] = A[j];
A[j] = tmp;
}
}
时间复杂度 O ( log ( l A − k ) + k ) O(\log (l_A-k)+k) O(log(lA−k)+k),空间 O ( 1 ) O(1) O(1)。