題目
劍指 Offer 39. 陣列中出現次數超過一半的數字
思路1(排序)
- 因為題目說一定會存在超過陣列長度一半的一個數字,所以我們將陣列排序後,位於
length/2
位置的一定是眾數
程式碼
class Solution {
public int majorityElement(int[] nums) {
Arrays.sort(nums);
return nums[nums.length/2];
}
}
複雜度分析
- 時間複雜度:\(O(NlogN)\)
- 空間複雜度:\(O(1)\)
思路2(雜湊表)
- 遍歷一遍陣列,將陣列的值作為鍵,出現的次數作為值,存放到雜湊表中
- 遍歷雜湊表,找到出現次數最多的那一個鍵值對就是我們要的眾數
程式碼
class Solution {
public int majorityElement(int[] nums) {
HashMap<Integer, Integer> map = new HashMap<>();
for (int n : nums) {
map.put(n, map.getOrDefault(n, 0) + 1);
}
int max = 0;
int res = 0;
for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
if (max < entry.getValue()) {
max = entry.getValue();
res = entry.getKey();
}
}
return res;
}
}
複雜度分析
- 時間複雜度:\(O(N)\)
- 空間複雜度:\(O(N)\)
思路3(分治)
- 將陣列從中間開始不斷分成兩份,直到只剩下一個元素時候開始返回
- 如果left和right出現的頻率一樣,直接返回
- 計算left和right在lo~hi範圍內出現的頻率,將高頻率的返回
- 為什麼可以用這個方法?比如有
[1, 2, 3, 2, 2, 2, 5, 4, 2]
,共有9個元素,共會被分成5份(每份一個或者兩個元素),然後從每份中再獲取一個值,共獲取5個值,又因為眾數的個數要大於陣列的長度,所以,眾數一定會至少被選中一個,只要被選中一個,那麼我們的countInRange
函式會根據範圍幫我們計算出最多出現的元素個數即眾數
程式碼
class Solution {
public int majorityElement(int[] nums) {
return majorityElementRec(nums, 0, nums.length-1);
}
public int majorityElementRec(int[] nums, int lo, int hi) {
// 如果只有一個元素,那麼直接返回
if (lo == hi) {
return nums[lo];
}
// 獲取中間值
int mid = lo + (hi - lo) / 2;
// 遞迴左邊和右邊,直到剩下一個元素
int left = majorityElementRec(nums, lo, mid);
int right = majorityElementRec(nums, mid+1, hi);
// 相等只需要返回一個
if (left == right) {
return left;
}
// 計算left和right在lo~hi範圍中的出現的次數
int leftCount = countInRange(nums, left, lo, hi);
int rightCount = countInRange(nums, right, lo, hi);
// 返回出現次數多的數
return leftCount > rightCount ? left : right;
}
// 統計目標數字在指定範圍出現的次數
public int countInRange(int[] nums, int target, int lo, int hi) {
int count = 0;
for (int i = lo; i <= hi; i++) {
if (nums[i] == target) {
count++;
}
}
return count;
}
}
複雜度分析
- 時間複雜度:\(O(NlogN)\)
- 空間複雜度:\(O(logN)\),遞迴過程中使用了額外的棧空間
思路4(摩爾投票法)
- 眾數記為+1,把其他數記為-1,將它們全部加起來,和最終會大於0
- 假設眾數記為
res
,票數記為votes
,假設有這麼一個陣列:[1, 2, 3, 2, 2, 2, 5, 4, 2],使用摩爾投票法的過程如下:
i=0
時:因為當前票數為0,所以將1賦值給res
,同時票數也加一。此時res=1 votes=1
i=1
時:2不等於1,所以票數要減1,此時res=1 votes=0
i=2
時:因為票數為0,所以眾數res要指向當前的3,然後票數加一,此時res=3 votes=1
i=3
時:2不等於3,所以票數要減1,此時res=3 votes=0
i=4
時:因為票數為0,所以眾數res要指向當前的2,然後票數加一,此時res=2 votes=1
i=5
時:由於2=2
,所以票數加一,此時res=2 votes=2
i=6
時:5不等於2,所以票數要減1,此時res=2 votes=1
i=7
時:4不等於2,所以票數要減1,此時res=2 votes=0
i=8
時:因為票數為0,所以眾數res要指向當前的2,然後票數加一,此時res=2 votes=1
- 最後就直接輸出res即為眾數
程式碼
class Solution {
public int majorityElement(int[] nums) {
// 代表結果的眾數
int res = nums[0];
// 統計票數
int votes = 0;
for (int i = 0; i < nums.length; i++) {
// 剛開始是0票,所以把陣列的第一個元素作為眾數
// 如果以後的迴圈votes票數被抵消掉了為0,那麼下一個元素就作為眾數
if (votes == 0) {
res = nums[i];
}
// 和當前眾數相同的,那麼票數就加1
if (res == nums[i]) {
votes++;
} else {
// 如果和當前票數不同,票數就被抵消掉了一個
votes--;
}
}
return res;
}
}
複雜度分析
- 時間複雜度:\(O(N)\)
- 空間複雜度:\(O(1)\)