我只能说lee215 yyds!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
class Solution {
private int target;

public List<Integer> findClosestElements(int[] arr, int k, int x) {
List<Integer> result = new ArrayList<>();
if (x <= arr[0]) {
for (int i = 0; i < k; i++) {
result.add(arr[i]);
}
return result;
}
if (x >= arr[arr.length - 1]) {
for (int i = arr.length - k; i < arr.length; i++) {
result.add(arr[i]);
}
return result;
}
int left = binarySearch(arr, x), right = left + 1, count = 0;
while (count != k) {
if (left >= 0 && right < arr.length) {
if (compare(arr, left, right, x) <= 0) {
result.add(arr[left]);
left -= 1;
} else {
result.add(arr[right]);
right += 1;
}
} else if (left >= 0) {
result.add(arr[left]);
left -= 1;
} else {
result.add(arr[right]);
right += 1;
}
count += 1;
}
Collections.sort(result);
return result;
}

private int binarySearch(int[] arr, int x) {
int left = 0, right = arr.length - 1, ans = -1;
while (left <= right) {
int middle = left + (right - left) / 2;
if (arr[middle] == x) {
return middle;
} else if (arr[middle] < x) {
ans = middle;
left = middle + 1;
} else {
right = middle - 1;
}
}
return ans;
}

private int compare(int[] arr, int i, int j, int x) {
if (Math.abs(arr[i] - x) != Math.abs(arr[j] - x)) {
return Math.abs(arr[i] - x) - Math.abs(arr[j] - x);
}
return -1;
}
}

这是我的尝试. Binary Search. 首先找到比x小的数字里面最大的(之前的模板用上了哈哈哈哈). 然后从这里开始两个指针分别向两边扩展.

改进版就是不要变走边添加, 而是只加下index, 比如left更好就让left–, right更好就让right++. 等到count == k的时候, 夹在left和right之间的就是我们的答案(不包括left和right因为left和right指向的是下一个被examined的地方而不是已经通过测试的地方). 这样就不用最后sort了. 于是时间复杂度就是O(logn + 2k)

时间复杂度: O(logn + k + klogk) = O(klogk)
空间复杂度: O(k) 用来存答案.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public List<Integer> findClosestElements(int[] arr, int k, int x) {
int left = 0, right = arr.length - 1;
List<Integer> ans = new ArrayList<>();
while (right - left + 1 > k) {
if (Math.abs(arr[left] - x) > Math.abs(arr[right] - x)) {
left += 1;
} else {
right -= 1;
}
}
for (int i = left; i <= right; i++) {
ans.add(arr[i]);
}
return ans;
}
}

第二种方法, 双指针, 这个方法比较直接, 直接从两头开始缩, 一直缩到left和right框起来的元素个数是k个(两头inclusive). 如果left距离x更远, 那么left肯定不能是最终答案的一员, 此时让left + 1. 如果left距离x更近那么肯定right不可能是最终答案的一员, 因为right左侧还有更好的. 一直这样缩, 缩到只剩k个, 这k个就是答案.

时间复杂度: O(N - k)
空间复杂度: O(k) 用来存答案.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public List<Integer> findClosestElements(int[] A, int k, int x) {
int left = 0, right = A.length - k;
while (left < right) {
int mid = (left + right) / 2;
if (x - A[mid] > A[mid + k] - x)
left = mid + 1;
else
right = mid;
}
List<Integer> ans = new ArrayList<>();
for (int i = left; i < left + k; i++) {
ans.add(A[i]);
}
return ans;
}
}

这个比较方法是真的牛逼. x - A[mid]和A[mid + k] - x比. 我们可以知道最后的答案A[start]一定小于等于A[start + k]. 如果A[start]大于A[start + k]那么我们至少能找到一个数字更接近x, 于是此时的start并不是答案. 但是满足这个条件的也不一定是最终答案, 只是可能是. 于是判定结果就出来了. 如果x在mid的左侧, 那么我们应该让mid往左边移动, 如果x在mid + k的右边, 那么我们应该让mid往右边移动, 如果在mid和mid + k之间, 我们需要判定. 如果距离mid近, 那么此时的mid可能是答案, 于是让right = mid, 也就是把mid框进sliding window中, 继续寻找, 看有没有更好的. 如果距离mid + k近, 那么mid肯定不是答案因为不符合答案的特征. 如果距离mid和mid + k距离一样, 那么mid可能是答案, 我们试着往左走, 看有没有可能取到更好的答案, 因为我们知道如果两个数字距离x的距离相等, 那么小的数字应该被框起来, 于是有这么一种情况, mid + k左侧的值和mid + k一样, mid左侧的值和mid一样, 那么如果我们让框起来的值从mid - 1开始, 那么此时仍然满足答案的要求, 但是框起来的数字会变得更小.

综上就是在保证框起来的数字到x的距离最小的前提下, start越靠左越好.

时间复杂度: O(log(N - k) + k)
空间复杂度: O(k) 用来存答案.