
1.本题最不动脑子的做法便是设置一个哈希表,遍历数组的同时给元素对应位置的哈希表值+1,同时判断该值是否大于numsSize / 2,可写出完整代码如下:
cpp
1. int majorityElement(int* nums, int numsSize) {
2. int hash[2000000001] = {0};
3. for (int i = 0; i < numsSize; i++){
4. hash[nums[i] + 1000000000]++;
5. if (hash[nums[i] + 1000000000] > numsSize / 2){
6. return nums[i];
7. }
8. }
9.
10. return nums[0];
11. }
该算法在逻辑上完全没有问题,但因为要考虑题目给出的元素数据范围为"-10^9 ~ 10^9",设置的哈希表范围也非常大,在该函数的栈上存不下这么多数据,会导致栈溢出,无法通过力扣的本地测试。该算法时间复杂度为O(n),空间复杂度为O(m),m为数据范围。
2.当题目保证一定存在出现次数大于n/2的元素时,就可以使用摩尔投票法。记录当前元素的值num_cur,这个值可以理解为擂主,出现一次相同的值就将计数值+1,反之则-1。当计数值减为负数,擂主就换成当前元素,因为最终出现次数大于n/2的元素一定会站在擂台最后,其他所有元素的出现次数和都小于它的次数。
3.基于以上思想,可写出完整代码如下:
cpp
1. int majorityElement(int* nums, int numsSize) {
2. // cur_num:摩尔投票的候选数;cnt:候选的计票
3. int cur_num = nums[0];
4. int cnt = 1;
5. // 从第2个元素开始遍历数组
6. for (int i = 1; i < numsSize; i++){
7. if (nums[i] == cur_num){
8. // 当前元素等于候选,票数+1
9. cnt++;
10. } else {
11. // 当前元素不等于候选,票数抵消-1
12. cnt--;
13. // 票数小于0,说明候选被抵消完,更换新候选,新候选票数初始化为1
14. if (cnt < 0){
15. cnt = 1;
16. cur_num = nums[i];
17. }
18. }
19. }
20. return cur_num;
21. }
该算法时间复杂度为O(n),空间复杂度为O(1)。
4.非常神奇的一点是,如果将票数判断条件从cnt < 0改为cnt == 0,本题依然是正确的。按照正常的理解来说,这会导致新的元素被多算了一次,但实际上的逻辑为:
(1)如果是虚假的多数元素被推翻,那么真正的多数元素即使多加了1次仍然是多数,不影响。
(2)如果是真正的多数元素被暂时推翻,少数元素多加了1次,但因为多数元素一定严格大于其他所有元素的次数和,也就是至少大于1,所以它一定能卷土重来,推翻现在的少数元素擂主,之后它也多加了1次,将这个差距补回来了。
本质上只是凑巧对了,实际解题过程中还是应该使用cnt < 0这种正常逻辑。
5.本题题解给了非常多的解法,包括但不限于:
(1)哈希表
(2)摩尔投票法
(3)排序法,下标为n/2的元素一定是答案
(4)随机化法,随机挑选一个元素来验证它是不是答案
(5)分治法
从复杂度角度来说,还是摩尔投票法是本题最优解。