【面试题029】数组中出现次数超过一半的数字
题目:
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
例如输入一个长度为9的数组{1, 2, 3, 2, 2, 2, 5, 4, 2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2.
思路一:
对数组排序最快O(n*logn),排序后统计每个数字出现的次数,只需要遍历一遍就可以知道那个数字出现次数最多。
排序后,出现次数超过数组长度一半的那个一定出现在中间。
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 |
#include <iostream>
#include <algorithm> using namespace std; int MoreThanHalfNum(int *numbers, int length) { //先对数组进行排序 sort(numbers, numbers + length); for (int i = 0; i < length; ++i) { cout << numbers[i] << " "; } cout << endl; return numbers[length / 2]; } int main() { int numbers[] = {1, 2, 3, 2, 2, 2, 5, 4, 2}; int num; num = sizeof(numbers) / sizeof(numbers[0]); cout << MoreThanHalfNum(numbers, num) << endl; return 0; } |
思路二:
基于快速排序Partition函数的O(n)的算法。
如果数组中一个数字出现的次数超过数组长度的一半,那么如果把这个数组排序后位于中间的数字一定是那个出现次数超过数组长度一半的数字。也就是说这个数字就是统计学上面“中位数”——长度为n的数组中第n/2大的数字。
我们有比较成熟的O(n)的算法得到数组中任意第k大的数字。
1.随机快速排序,我们线从数组中选一个数字,然后调整数组中数字的顺序,比选中数字小的数字都排在它的左边,比选中数字大的数字都排在它的右边。划分完毕后,如果选中数字的下标刚好是n/2,那么这个数字就是数组的中位数。
2.如果下标大于n/2,那么中位数位于它的左边。接着在左边查找。
3.如果下标小于n/2,那么中位数位于它的右边。接着在右边查找。
——这是一个典型的递归过程。
C++ Code
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 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
#include <iostream>
#include <exception> using namespace std; /* * 交换数组中的两个元素 */ void Swap(int *a, int *b) { int tmp; tmp = *a; *a = *b; *b = tmp; } /* * 做一次划分 * small指向左边区域的最右边, * 初始值为beg的前一个位置,表示初始时候左边区域为空的 * index指向当前要处理的那个元素,要判断这个元素属于左边还是右边 * beg……small small+1……index-1 index……end */ int Partition(int *numbers, int beg, int end) { //如果轴是随机取的话,这里得做一个Swap把这个轴元素交换到end位置 int small = beg - 1; int index; for (index = beg; index < end; ++index) { if (numbers[index] <= numbers[end]) { small++; Swap(&numbers[small], &numbers[index]); } } ++ small; Swap(&numbers[small], &numbers[end]); return small; } /* * 这并不是递归,只不过是多次调用同一个函数而已 * 但是每次调用传递过去的参数是不同的 */ int MoreThanHalfNum(int *numbers, int length) { int middle = length >> 1; int beg = 0; int end = length - 1; int index = Partition(numbers, beg, end); while (index != middle) { if (index > middle) { end = index - 1; index = Partition(numbers, beg, end); } else { beg = index + 1; index = Partition(numbers, beg, end); } } int result = numbers[middle]; return result; } int main() { int numbers[9] = {1, 4, 4, 3, 4, 3, 4, 4, 2}; int num = sizeof(numbers) / sizeof(numbers[0]); cout << MoreThanHalfNum(numbers, num) << endl; return 0; } |
思路三:
数组中一个数字出现的次数超过数组长度的一半,说明这个数字出现的次数,超过其他数字出现的总和。
遍历这个数组的过程当中,维护两个变量,一个是times次数,一个是数组中的一个数字result,
初始化时候result = numbers[0], times = 1,
遍历的过程当中如果数字等于result,则times++,如果不等于的话times--,
在修改times之前需要判断下times是否等于0,如果等于0的话,把当前遍历到的那个值复制给result,并且把times赋值为1,
——我们要找的数字就是那个最后一次把times赋值为1的那个数。
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 64 65 66 67 68 69 70 71 72 73 74 75 76 |
#include <iostream>
using namespace std; bool g_bInputInvalid = false; bool CheckInvalidArray(int *numbers, int length) { g_bInputInvalid = false; if (numbers == NULL || length <= 0) { g_bInputInvalid = true; } return g_bInputInvalid; } bool CheckMoreThanHalf(int *numbers, int length, int number) { int times = 0; for (int i = 0; i < length; ++i) { if (numbers[i] == number) { times ++; } } bool isMoreThanHalf = true; if (times * 2 <= length) { g_bInputInvalid = true; isMoreThanHalf = false; } return isMoreThanHalf; } int MoreThanHalfNUm(int *numbers, int length) { if (CheckInvalidArray(numbers, length)) { return 0; } int result = numbers[0]; int times = 1; for (int i = 1; i < length; ++i) { if (times == 0) { result = numbers[i]; times = 1; } else if (numbers[i] == result) { times++; } else { times--; } } if (!CheckMoreThanHalf(numbers, length, result)) { result = 0; } return result; } int main() { int numbers[] = {1, 2, 3, 2, 2, 2, 5, 4, 2}; int num = sizeof(numbers) / sizeof(numbers[0]); cout << MoreThanHalfNUm(numbers, num) << endl; return 0; } |