携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第27天,点击查看活动详情
题目
给定一个 排序好 的数组 arr ,两个整数 k 和 x ,从数组中找到最靠近 x(两数之差最小)的 k 个数。返回的结果必须要是按升序排好的。
整数 a 比整数 b 更接近 x 需要满足:
|a - x| < |b - x| 或者 |a - x| == |b - x| 且 a < b
示例 1:
输入:arr = [1,2,3,4,5], k = 4, x = 3
输出:[1,2,3,4]
示例 2:
输入:arr = [1,2,3,4,5], k = 4, x = -1
输出:[1,2,3,4]
提示:
1 <= k <= arr.length 1 <= arr.length <= 104 arr 按 升序 排列 -104 <= arr[i], x <= 104
题解
根据题目描述,我们可以知道其中有几个重要的信息。首先:这个数组arr是排序好了的,并且要求返回的结果也是排序的。那么,我们可以推测出来,最终的结果也就是原数组arr的一个子集。
那么,我们就可以先根据题目中给的查找值x,去确定一下所在数组arr的下标位置midIndex。但是在查找过程中,如果查找到了相同值还好办,如果没有查找到与x相同的值,那怎么办呢?这里我们可以通过x与数组arr中每个元素进行判断,如果我们第一次发现第i个元素大于等于x了,那么就说明,midIndex的值要么是i,要么就是i-1,具体取哪个值,我们可以通过判断i和i-1这两个元素与x的差值哪个小,我们就将midIndex指向该位置。
判断完毕midIndex的值之后,我们就可以以它为中心点,向左或者向右进行发散操作。最初我们可以创建两个变量,分别为开始索引startIndex和结束索引endIndex,最初这两个值与midIndex都是相同的。那么我们可以根据方法入参k(最终数组长度)来确定startIndex或endIndex要移动的次数,在每次移动操作之前,我们都要对比arr[startIndex - 1]和arr[endindex + 1]这两个元素,如果对比的结果是小于等于,那么startIndex向前移动一位,即:startIndex++;反之,如果对比的结果是大于,那么则endIndex向后移动一位,即:endIndex--;直到循环次数完毕,组装最终的结果作为方法的返回值。
由于数组是有序的,因此最接近的k个元素一定是在数组中连续的,因此我们维护两个左右指针
var findClosestElements = function(arr, k, x) {
const list = [...arr];
list.sort((a, b) => {
if (Math.abs(a - x) !== Math.abs(b - x)) {
return Math.abs(a - x) - Math.abs(b - x);
} else {
return a - b;
}
});
const ans = list.slice(0, k);
ans.sort((a, b) => a - b);
return ans;
};
代码详解
容易想到先通过「二分」找到与 x 差值最小的位置 idx,然后从 idx 开始使用「双指针」往两边进行拓展(初始化左端点 i = idx - 1i=idx−1,右端点 j = idx + 1j=idx+1),含义为 [i + 1, j - 1][i+1,j−1] 范围内子数组为候选区间,不断根据两边界与 x 的差值关系进行扩充,直到候选区间包含 kk 个数。我们要想找到K个最接近的元素,可以删除n-k的元素来达到目的。