数组中出现超过一半的数字
一、题目
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
二、解答
说明:
1、下面代码都没有判断数组本身是否为空,需要自己判断
2、找到一个数以后,需要判断这个数字是否满足“出现次数超过一半”的条件,代码如下
bool CheckMoreThanHalf(vector<int>&nums, int number) { int times = 0; for (int i = 0; i < nums.size(); i++) { if (nums[i] = number) { times++; } } bool isMoreThanHalf = true; if (times * 2 <= nums.size()) { isMoreThanHalf = false; } return isMoreThanHalf; }
解法一:排序,取中位数
直观的想法, 如果一个数组某个数字出现超过一半,则排序后的数组的中位数必为所求。
缺点:排序的时间复杂度较高
int moreThanHalfNums1(vector<int>& nums) { sort(nums.begin(), nums.end()); int mid = nums.size() / 2; int result = nums[mid]; if (!CheckMoreThanHalf(nums, result)) { return 0; } return result; }
解法二:空间换时间,利用哈希表
借助hashmap存储数组中每个数出现的次数,最后看是否有数字出现次数超过数组长度的一半
int moreThanHalfNums2(vector<int>& nums) { unordered_map<int, int> mp; for (int i = 0; i < nums.size(); i++) { if (mp.count(nums[i] == 0)) { mp[nums[i]] = 1; } else { mp[nums[i]]++; } } int count = 0; int res = 0x0fffffff; for (unordered_map<int, int>::iterator it = mp.begin(); it != mp.end(); it++) { if (it->second * 2 > nums.size()) { res = it->first; break; } } if (res != 0x0fffffff) { return res; } return -1; }
解法三:基于Partition函数的O(n)算法
同解法一类似,数组中有一个数字出现的次数超过了数组长度的一半。如果把这个数组『排序』,那么排序之后位于数组中间的数字一定就是那个出现次数超过数组长度一半的数字。也就是说,这个数字就是统计学上的『中位数』,即长度为n的数组中第n/2大的数字。和解法一不同的是,我们没必要对数组全部排序。我们有成熟的O(n)的算法得到数组中任意第k大的数字。
思想:
如何实现在 O(n) 的算法复杂度中找到任意第 k 大的数。『快速排序』第一趟排序之后,我们的到了一个中间索引index,那么排在中间 index 前面的值都比 nums[index] 小;同样的排序在中间 index 后面的元素都比 nums[index] 大。第一趟的时间复杂度为O(n/2)。排序完之后,我们获得了两个子序列,同样根据上述的逻辑进行排序,假设当 k < index 时,那么我们就继续排序第二段比 index 小的部分。这一次的时间复杂度为O(n/4)。以此类推,当我们把 index 定位到 k 的时候,我们就会清楚的知道,在 k 以前的数字都比 nums[k] 的值小。这也就快速的找到了任意 k 大的数组,时间复杂度 O(n) ≈ O(n/2) + O(n/4) + O(n/8) + ⋯ + O(n/2n)。
int partition(vector<int>& nums, int left, int right) { int key = right; while (left < right) { while (left < right && nums[left] <= nums[key]) { ++left; } while (left < right && nums[right] >= nums[key]) { --right; } swap(nums[left], nums[right]); } swap(nums[left], nums[key]); return left; } int moreThanHalfNums3(vector<int>& nums) { int length = nums.size(); int mid = length >> 1; int start = 0; int end = length - 1; int index = partition(nums, start, end); while (index != mid) { if (index > mid) { end = index - 1; index = partition(nums, start, end); } else { start = index + 1; index = partition(nums, start, end); } } int result = nums[mid]; if (!CheckMoreThanHalf(nums, result)) { return 0; } return result; }
解法四:根据数组特点找出O(n)的算法
数组中有一个数字出现的次数超过数组长度的一半,也就是说它出现的次数比其他所有数字出现次数的和还要多。因此我们可以考虑在遍历数组的时候保存两个值:一个是数组中的一个数字,一个是次数。
- 当我们遍历到下一个数字的时候,如果下一个数字和我们之前保存的数字相同,则次数加1;
- 如果下一个数字和我们之前保存的数字不同,则次数减1。
- 如果次数为零,我们需要保存下一个数字,并把次数设为1。
由于我们要找的数字出现的次数比其他所有数字出现的次数之和还要多,最后,只有那个超过一半的数不会减退到0。
int moreThanHalfNums4(vector<int>& nums) { int num = nums[0]; int count = 1; for (int i = 1; i < nums.size(); i++) { if (nums[i] == num) { ++count; } else { if (count == 0) { num = nums[i]; count = 1; } else { --count; } } } int result = num; if (!CheckMoreThanHalf(nums, result)) { return 0; } return result; }