面试算法:快速获取数组中点的相邻区域点
2017-08-22 17:47
204 查看
假设给定一个数组A,要求你找到数组的中点,并且将离中点最近的k个数组成员抽取出来。例如数组A={7, 14, 10, 12, 2, 11, 29, 3, 4},该数组的中点是10,如果令k=5,那么里中点最近的5个数就是{7,14,10,12,11}.
要求对任意一个数组,设计一个时间复杂度为O(n)的算法,该算法能找出距离数组中点最近的K个元素。
这道题的难度是比较大的,在面试中出现的概率应该不大,如果你能在一小时内把它解决并给出代码,那么你的水平在BAT中当个技术经理以上的职位应该是没有太大问题的。
解决这个问题要分两步走:
1, 找到一个算法,能够让我们在O(n)的时间内查找到数组的中点。
2, 假设我们找到了中点m, 那么我们把计算数组前k个元素与中点m的距离,也就是 d(i) = |A[i] - m|, 把这k个元素与中点的距离构建一个大堆。接着在剩下的n - k 个元素中,我们逐次计算他们与中点m的距离,然后用这个距离与大堆中的最大值相比较,如果这个距离大过大堆返回的最大值,那么就忽略这个元素,如果它的距离比大堆的最大值小,那么就把大堆中的最大值去掉,将当前元素加入大堆。
当完成步骤2之后,大堆中的k个元素就是距离终点最近的K个元素。在第二步中,我们需要把元素加入一个大堆,对于一个含有k个元素的大堆而言,加入一个元素的复杂度是O(lgk),所以执行第二步所需的时间复杂度是O(n * lgk)。 由于k是一个常数,所以第二步相对于变量n来说,也是线性的。
现在问题在于第一步的实现,也就是如何通过线性时间的算法找到一个数组的中点,事实上,只要找到第一步的算法,那么整个问题就可以解决了,本质上第二步是多余的,所以本文的重点将在对一步的分析上。我们看看如何设计一个算法,使得我们能在O(n)的时间复杂度内,在数组中找到任意第k大的元素。显然,数组中点是数组中第n/2大的元素,于是要解决问题,我们只要找到第(n/2-k/2)大的元素,然后再找到(n/2+k/2)大的元素,最后查找所有处于这两者之间的元素,那么问题就解决了。
我们看看如何在O(n)的时间内,找到数组中第i大的元素,它的算法步骤如下:
1, 把n个元素分成若干个小组,每个小组有5个元素,于是总共能分成n/5组
2, 对每个小组中的五个元素进行排序,然后取出他们的中点。
3, 利用本算法递归的去查找步骤2中所有中点组成的集合中的中点,假定得到的中点为x。
4, 根据步骤4得到的x把数组划分为两部分,把所有小于x的元素放到x的左边,把所有大于x的元素放到x的右边。假设x的左边有k-1个元素,那么x就是第k小的元素,在x的右边就有n-k个元素。
5, 如果i == k, 那么直接返回x, 如果 i < k, 那么我们在x左边元素集合中,递归的使用该算法去查找第i 小的元素。如果i > k, 那么我们在x的右边元素集合中,使用该算法去查找第(i - k)小的元素。
该算法的实现代码如下:
我们简单对算法进行分析一下:
第一步是把元素分成若干个小组,一次循环就可以完成,所以复杂度是O(n);
第二步是找到每个小组中的中点,由于每个小组只有5个元素,所以第二步的时间复杂度也是O(n);
第三步是在所有中点的集合中再找出中点,这一步涉及到算法的递归,所以我们暂时不做分析。
第四步是把数组元素分成两部分,所需时间复杂度也是O(n)。
第五步是在数组中的某一个子集中再次递归的运行算法。
我们看看执行第3步后,也就是取到中点的中点,假设中点的中点为x,然后我们会把数组元素根据x分成两部分:
我们看上图,假设中间点x是我们找到的中点集合中的中点,这样右下角阴影部分的节点,他们的值肯定是大于x的。因为我们把所有节点分成若干个小组,每个小组有5个点,最后一个小组有可能不足5个元素,所有去掉包含x的那个小组,以及最后一个小组,那么就有 (1/2 * n/5) 个小组中,下半部的三个节点都会大于x.于是大于x的元素的个数就至少有:
3* [ (1/2 *n/5) - 2] = 3n/10 - 6 个。
如果我们要找的元素处于前半部分,那么前半部分的元素个数最多有 n - (3n/10 - 6) = 7n/10 + 6 个,如果处于后半部分,那么要处理的元素就有3n/10 - 6 个,也就是说,当我们执行第5步的时候,递归处理的元素个数最多不超过7n/10 + 6个。
我们不深入更加细致的复杂度分析,可以保证的是,算法的最终复杂度是O(n), 也就是说依靠上面算法实现,我们可以在线性时间内从数组中查找处于任意位置的元素。
根据给定的数组A = {7, 14, 10, 12, 2, 11, 29, 3, 4}, 它排序后为:{2, 3, 4, 7, 10, 11, 12, 14, 29},于是第0小的元素是2,第8小的元素是29.如果运行代码:
int y = select(A, 8);
那么我们得到的y值就是29。对代码更详细的讲解和调试演示,请参看视频:
如何进入google,算法面试技能全面提升指南
更多技术信息,包括操作系统,编译器,面试算法,机器学习,人工智能,请关照我的公众号:
要求对任意一个数组,设计一个时间复杂度为O(n)的算法,该算法能找出距离数组中点最近的K个元素。
这道题的难度是比较大的,在面试中出现的概率应该不大,如果你能在一小时内把它解决并给出代码,那么你的水平在BAT中当个技术经理以上的职位应该是没有太大问题的。
解决这个问题要分两步走:
1, 找到一个算法,能够让我们在O(n)的时间内查找到数组的中点。
2, 假设我们找到了中点m, 那么我们把计算数组前k个元素与中点m的距离,也就是 d(i) = |A[i] - m|, 把这k个元素与中点的距离构建一个大堆。接着在剩下的n - k 个元素中,我们逐次计算他们与中点m的距离,然后用这个距离与大堆中的最大值相比较,如果这个距离大过大堆返回的最大值,那么就忽略这个元素,如果它的距离比大堆的最大值小,那么就把大堆中的最大值去掉,将当前元素加入大堆。
当完成步骤2之后,大堆中的k个元素就是距离终点最近的K个元素。在第二步中,我们需要把元素加入一个大堆,对于一个含有k个元素的大堆而言,加入一个元素的复杂度是O(lgk),所以执行第二步所需的时间复杂度是O(n * lgk)。 由于k是一个常数,所以第二步相对于变量n来说,也是线性的。
现在问题在于第一步的实现,也就是如何通过线性时间的算法找到一个数组的中点,事实上,只要找到第一步的算法,那么整个问题就可以解决了,本质上第二步是多余的,所以本文的重点将在对一步的分析上。我们看看如何设计一个算法,使得我们能在O(n)的时间复杂度内,在数组中找到任意第k大的元素。显然,数组中点是数组中第n/2大的元素,于是要解决问题,我们只要找到第(n/2-k/2)大的元素,然后再找到(n/2+k/2)大的元素,最后查找所有处于这两者之间的元素,那么问题就解决了。
我们看看如何在O(n)的时间内,找到数组中第i大的元素,它的算法步骤如下:
1, 把n个元素分成若干个小组,每个小组有5个元素,于是总共能分成n/5组
2, 对每个小组中的五个元素进行排序,然后取出他们的中点。
3, 利用本算法递归的去查找步骤2中所有中点组成的集合中的中点,假定得到的中点为x。
4, 根据步骤4得到的x把数组划分为两部分,把所有小于x的元素放到x的左边,把所有大于x的元素放到x的右边。假设x的左边有k-1个元素,那么x就是第k小的元素,在x的右边就有n-k个元素。
5, 如果i == k, 那么直接返回x, 如果 i < k, 那么我们在x左边元素集合中,递归的使用该算法去查找第i 小的元素。如果i > k, 那么我们在x的右边元素集合中,使用该算法去查找第(i - k)小的元素。
该算法的实现代码如下:
int select(int[] A, int i) { //在数组中查找第i小的元素 if (A.length == 1) { return A[0]; } /* * 步骤1,将数组分成小组,每个小组含有5个元素,最后一组很可能没有5个元素 */ ArrayList<int[]> arrCollection = new ArrayList<int[]>(); int p = 0; int cnt = 5; int[] arr = null; while (p < A.length) { if (cnt >= 4) { int len = Math.min(5, A.length - p); arr = new int[len]; cnt = 0; arrCollection.add(arr); } else { cnt++; } arr[cnt] = A[p]; p++; } /* * 步骤2,把每个小组中的元素排序,并取出每个小组中的中点 */ int[] medians = new int[arrCollection.size()]; for (int j = 0; j < arrCollection.size(); j++) { Arrays.sort(arrCollection.get(j)); arr = (int[])arrCollection.get(j); medians[j] = arr[arr.length / 2]; } /* * 步骤3,递归的去获取中点集合中的中点 */ int x = select(medians, medians.length/2); /* * 步骤4,把小于x的元素放到x左边,把大于x的元素放到x的右边 */ int[] B = new int[A.length]; int begin = 0, end = A.length - 1; int pos = 0; while (pos < A.length) { if (A[pos] < x) { B[begin] = A[pos]; begin++; } if (A[pos] > x) { B[end] = A[pos]; end--; } pos++; } B[begin] = x; A = B; /* * 执行步骤5,如果x左边的元素是k-1个,同时i == k, 那么返回x * 如果i < k, 那么递归的去在左边k-1个元素中查找第i小的元素 * 如果i > k, 那么递归的在右边n-k个元素中,查找第(i - k)小的元素 */ if (i == begin) { return x; } else if (i < begin) { arr = Arrays.copyOf(A, begin); return select(arr, i); } else { arr = Arrays.copyOfRange(A, begin+1, A.length); return select(arr, i - begin - 1); } }
我们简单对算法进行分析一下:
第一步是把元素分成若干个小组,一次循环就可以完成,所以复杂度是O(n);
第二步是找到每个小组中的中点,由于每个小组只有5个元素,所以第二步的时间复杂度也是O(n);
第三步是在所有中点的集合中再找出中点,这一步涉及到算法的递归,所以我们暂时不做分析。
第四步是把数组元素分成两部分,所需时间复杂度也是O(n)。
第五步是在数组中的某一个子集中再次递归的运行算法。
我们看看执行第3步后,也就是取到中点的中点,假设中点的中点为x,然后我们会把数组元素根据x分成两部分:
我们看上图,假设中间点x是我们找到的中点集合中的中点,这样右下角阴影部分的节点,他们的值肯定是大于x的。因为我们把所有节点分成若干个小组,每个小组有5个点,最后一个小组有可能不足5个元素,所有去掉包含x的那个小组,以及最后一个小组,那么就有 (1/2 * n/5) 个小组中,下半部的三个节点都会大于x.于是大于x的元素的个数就至少有:
3* [ (1/2 *n/5) - 2] = 3n/10 - 6 个。
如果我们要找的元素处于前半部分,那么前半部分的元素个数最多有 n - (3n/10 - 6) = 7n/10 + 6 个,如果处于后半部分,那么要处理的元素就有3n/10 - 6 个,也就是说,当我们执行第5步的时候,递归处理的元素个数最多不超过7n/10 + 6个。
我们不深入更加细致的复杂度分析,可以保证的是,算法的最终复杂度是O(n), 也就是说依靠上面算法实现,我们可以在线性时间内从数组中查找处于任意位置的元素。
根据给定的数组A = {7, 14, 10, 12, 2, 11, 29, 3, 4}, 它排序后为:{2, 3, 4, 7, 10, 11, 12, 14, 29},于是第0小的元素是2,第8小的元素是29.如果运行代码:
int y = select(A, 8);
那么我们得到的y值就是29。对代码更详细的讲解和调试演示,请参看视频:
如何进入google,算法面试技能全面提升指南
更多技术信息,包括操作系统,编译器,面试算法,机器学习,人工智能,请关照我的公众号:
相关文章推荐
- 求数组相邻元素差值的最大值快速算法(C++版)
- 程序员如何快速准备面试中的算法-July
- 面试算法(二十八)数组中出现次数超过一半的数字
- 【LeetCode-面试算法经典-Java实现】【026-Remove Duplicates from Sorted Array(删除排序数组中的重复元素)】
- 面试算法题:数组中两个数之和为定值,找出这对数的下标
- C/C++面试之算法系列--去除数组中的重复数字
- 程序员如何快速准备面试中的算法
- 微软等数据结构+算法面试100题(23)--数组中超过出现次数超过一半的数字
- php面试算法题—快速算法、冒泡算法讲解
- 微软等数据结构+算法面试100题(40)-- 旋转数组中的最小元素
- CSDN大神JULY:程序员如何快速准备面试中的算法
- 程序员面试题目总结--数组(二)【二分查找、找出给定数字出现次数、两个有序整型数组交集、找出数组中唯一的重复元素、判断数组中的数值是否连续相邻】
- 程序员如何快速准备面试中的算法(转)
- 程序员如何快速准备面试中的算法
- 程序员如何快速准备面试中的算法
- 程序员如何快速准备面试中的算法
- 面试算法(三十八)数字在排序数组中出现的次数
- 数组的应用创建、存储、自定义、快速获取
- 程序员如何快速准备面试中的算法
- 阿里巴巴面试算法题目:有无序的实数列V[N],要求求里面大小相邻的实数的差的最大值,关键是要求线性空间和线性时间