Rearch Interest: Visualization">
169. 多数元素
题目描述
给定一个大小为\(n\)的数组,找到其中的多数元素。 多数元素是指在数组中出现次数大于 \(⌊ n/2 ⌋\) 的元素。 你可以假设数组是非空的,并且给定的数组总是存在多数元素。
示例 1: >输入:[3,2,3]
>输出: 3
示例 2: >输入:[2,2,1,1,1,2,2]
>输出:
2
进阶:
- 尝试设计时间复杂度为 \(O(n)\)、空间复杂度为 \(O(1)\) 的算法解决此问题。
我的代码 1: 排序
\(T(N) = O(NlogN)\), \(S(N) = O(N)\)
{.line-numbers} 1
2
3
4
5
6
7
8
9
10
11
12
class MySolution169_1
{
public int majorityElement(int[] nums)
{
Arrays.sort(nums);
if (nums.length%2==0)
return nums[nums.length/2-1];
else return nums[nums.length/2];
return nums[nums.length/2];
}
}
1 | class MySolution169_1 |
我的代码 2: 哈希表
\(T(N) = O(N)\), \(S(N) = O(N)\)
1 | class MySolution169_2 |
方法一: 分治法
\(T(N) = O(NlogN)\), \(S(N) = O(logN)\)
如果元素num
是数组nums
的众数,
那么将nums
分为两部分时,num
必定至少是其中某一部分的众数。
用反证法对这一结论进行证明:
设l为左半部分的长度,r
是右半部分的长度,
假设num
既不是左半部分的众数,也不是右半部分的众数,
那么num
的出现次数将小于(l/2+r/2)
次;
又由于(l/2+r/2)≤(l+r)/2
,说明num
也不是数组nums
的众数,因此出现了矛盾。
可见结论是正确的。
因此,可以将数组分为左右两部分,
分别求出左半部分的众数num1
和右半部分的众数num2
,
再在num1
和num2
之间选出正确的那个众数。
通过递归的分治算法来求解。 当所有子问题的长度都是1时,这唯一一个数显然就是众数,直接返回; 当回溯后区间长度大于1时, 若左右子区间的众数相同,则合并后区间的众数显然就是这个值; 若左右子区间的众数不同,则需要比较两个众数在合并后整个区间内的出现次数来决定取谁。
代码
1 | class Solution169 |
时空复杂度
- 时间复杂度\(O(NlogN)\):
函数
split()
会求解两个长度为\(\frac {n}{2}\)的子问题,并做两次长度为\(n\)的线性扫描。因此分治算法的时间复杂度可以表示为\(2T(\frac {n}{2})+2n\). 根据主定理,本题的时间复杂度为\[T(n)=\Theta(n^{log_ba}logn)=\Theta(n^{log_22}logn)=\Theta(nlogn)\] - 空间复杂度\(O(1)\).
方法二:摩尔投票法Boyer-Moore
\(T(N) = O(N)\), \(S(N) = O(1)\)
如果我们把众数记为+1
,其他数记为-1
,将它们全部加起来,和显然大于0
。
维护一个候选众数candidate
和其出现次数count
。初始candidate
任意,count
为0
.
遍历nums
中的元素。在判断nums[i]
之前,若count
为0
则将nums
设置为candidate
。
若nums[i]
与candidate
相等,则count
加1
;
若nums[i]
与candidate
不等,则count
减1
;
遍历结束后的candidate
就是众数。
摩尔投票算法的正确性较难证明,可以通过一个例子来理解.
nums
为数组,candidate
为候选众数,count
为候选众数的出现次数(非负),
value
为目前为止真正的众数比非众数多出现的次数
(如果nums[i]
是真正的众数则加1
,否则减1
)。
nums: [7, 7, 5, 7, 5, 1 | 5, 7 | 5, 5, 7, 7 | 7, 7, 7, 7] candidate: 7 7
7 7 7 7 5 5 5 5 5 5 7 7 7 7 count: 1 2 1 2 1 0 1 0 1 2 1 0 1 2 3 4
value: 1 2 1 2 1 0 -1 0 -1 -2 -1 0 1 2 3 4
可以发现
当candidate
就是众数时count
和value
相等,否则它们互为相反数。
这是因为:
我们将候选众数candidate
保持不变的连续的遍历称为「一段」。
在同一段中,count
的值根据candidate
是否等于nums[i]
进行加减。
那么如果candidate
就是众数,则在这一段中,count
和value
的变化是同步的;
如果candidate
不是众数,则在这一段中,count
和value
的变化是相反的。
由于value
的值与真正的众数绑定,
并且它表示「目前为止真正的众数比非众数多出现的次数」,
因此在遍历的最后,value
的值是正数。
即在最后一次遍历后,count
非负、value
为正数,
因此它们不会互为相反数,只可能相等。
可见在最后的「一段」中,count
和value
的变化是同步的,
即candidate
绑定的候选众数就是真正的众数。
代码
1 | class Solution169 |