面试算法:快速获取数组中点的相邻区域点
来源:互联网 发布:国内mba 知乎 编辑:程序博客网 时间:2024/06/06 17:49
假设给定一个数组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)小的元素。
该算法的实现代码如下:
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++版)
- 面试算法:波浪型数组的快速排序法
- 算法 给出一个数组,求连续相邻的最大面积
- 椭圆的中点生成算法
- 生成圆弧的中点算法
- 中点椭圆算法的实现
- 数组的几个面试算法
- 获取图片某点或区域的颜色 与 CGBitmapContextCreate
- 编程实现圆的中点算法
- 编程实现椭圆的中点算法
- 计算机图形学 - 抛物线的中点生成算法
- 任意斜率的中点画线算法
- 面试算法:二分查找法寻找数组截断点
- 如何快速获取图片的ROI区域坐标
- 一个消除高亮度点区域的算法
- 基于区域卷积的增强型点噪声算法
- 利用select_region_point算子可以通过区域内的一个点或者区域的一部分,获取完整区域
- 聊天程序(基于Socket、Thread)
- Hibernate的三种状态
- POJ
- 主线程Thread和子线程Thread的区别
- llvm 文件后缀转换
- 面试算法:快速获取数组中点的相邻区域点
- 【YOLO】数据集处理训练自己的数据集
- 151. php 统计字符
- Vue.js入门-内置指令v-text
- 组合模式
- python核心编程v2.0 第9章习题答案(上)
- 練習 2017-08-22 list的迭代方式
- Spring @Transactional注解不生效
- Linux手动安装sbt步骤