剑指offer面试题29:数组中出现次数超过一半的数字
Posted knis
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了剑指offer面试题29:数组中出现次数超过一半的数字相关的知识,希望对你有一定的参考价值。
题目:数组中有一个数字出现的次数超过数组长度的一般,请找出这个数字,例如输入一个长度为9的数组(1,2,3,2,2,2,5,4,2,)。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。
个人第一眼想法是通过一个sort函数,再判断中间那数出现次数,只要出现多于n/2,就直接输出。
一般来说,最为直观的算法面试官都不会满意,那么有没有更优的算法呢?
这种算法是受快速排序算法的启发。在随机快速排序算法中,我们现在数组中随机选择一个数字,然后调整数组中数字的顺序,使得比选中的数字小的数字都排在它的左边,比选中的数字大的数字都排在它的右边。如果这个选中的数字的下标刚好是n/2,那么这个数字就是数组的中位数。如果它的下标大于n/2,那么中位数应该位于它的左边,我们可以接着在它的左边部分的数组中查找。如果它的下标小于n/2,那么中位数应该位于它的右边,我们可以接着在它的右边部分的数组中查找。这是一个典型的递归过程,实现代码如下:
1 int MoreThanHalfNum_Solution(vector<int> numbers) 2 { 3 int length = numbers.size(); 4 if (numbers.empty() || length<0) 5 return 0; 6 7 int begin = 0; 8 int end = length - 1; 9 int middle = length >> 1; 10 int index = 0; 11 //利用 12 while (begin<end) 13 { 14 index = Partition(numbers, begin, end); 15 if (index == middle) 16 { 17 break; 18 } 19 else if (index > middle) 20 { 21 end = index - 1; //则值在左边部分的数组。 22 } 23 else 24 { 25 begin = index + 1;//则值在右边部分的数组。 26 } 27 } 28 //检查该值是否超过数组长度的一半 29 int cnt = 0; 30 for (int i = 0; i < length; ++i) 31 { 32 if (numbers[index] == numbers[i]) 33 cnt++; 34 } 35 if (cnt * 2 > length) return numbers[index]; 36 37 return 0; 38 }
一.基于partition函数的O(n)算法
算法一
partition函数思路:
- 使用第一个数组元素作为枢轴点,即为pivot;
- 使用一个指针去扫描整个数组,凡是小于pivot的全部放到数组左端;
- 最后将pivot放到数组中间的位置,pivot左边全部都是小于它的数字,右边反之,最后返回pivot的位置信息;
代码如下:
1 void swap(int &x, int &y) 2 { 3 int t = x; 4 x = y; 5 y = t; 6 7 } 8 int partition(vector<int> &nums, int begin, int end) 9 { 10 int pivot = nums[begin];//枢轴(也可以是在begin和end之间的随机数) 11 // Last position where puts the no_larger element. 12 //凡是小于pivot的全部放到数组左端,pos指向<枢轴值的最后一个 13 //pos++指向不满足条件的(用于交换,将满足条件的换过来) 14 int pos = begin; 15 for (int i = begin + 1; i < end; ++i) 16 { 17 if (nums[i] < pivot) 18 { 19 pos++; 20 if (i != pos) //避免自身交换 21 swap(nums[pos], nums[i]); 22 } 23 24 } 25 swap(nums[pos], nums[begin]); 26 return pos; 27 }
算法分析
这种实现思路比较直观,但是其实并不高效。从直观上来分析一下,每个小于pivot的值基本上(除非到现在为止还没有遇见大于pivot的值)都需要一次交换,大于pivot的值(有可能需要被交换多次才能到达最终的位置。
算法二
算法思路
- 就如快速排序中最常使用的那样,使用两个指针分别从头部和尾部进行扫描,头部遇到大于pivot的数和尾部遇到小于pivot的数进行交换;
- 使用了两个指针,效率更高一点;避免使用swap函数
如果我们考虑用 Two Pointers 的思想,保持头尾两个指针向中间扫描,每次在头部找到大于pivot的值,同时在尾部找到小于pivot的值,然后将它们做一个交换,就可以一次把这两个数字放到最终的位置。一种比较明智的写法如下:
//Two Pointers思想的分割函数(begin为0,end为n-1) int Partition(vector<int> &nums, int begin, int end) { int pivot = nums[begin];//第一个记录作为枢轴(也可是在begin和end之间的随机数) while (begin < end) { while (begin < end && nums[end] >= pivot) { end--; } nums[begin] = nums[end];//尾部找到小于pivot的值,移到低端 while (begin < end && nums[begin] <= pivot) { begin++; } nums[end] = nums[begin];//头部找到大于pivot的值,移到高端 } nums[begin] = pivot;//枢轴基准归位 return begin; }
算法分析:赋值操作不多,效率会更高
二分Partition算法
快速排序算法
void quickSort(vector<int> &nums, int begin, int end) { if (begin >= end) return; int index = partition(nums, begin, end); if (index>begin) quickSort(nums, begin, index-1); if (index<end) quickSort(nums, index+1, end); } 调用: quickSort(vec, 0, vec.size()-1); //end为n-1
以上是关于剑指offer面试题29:数组中出现次数超过一半的数字的主要内容,如果未能解决你的问题,请参考以下文章
剑指OFFER----面试题39. 数组中出现次数超过一半的数字