優化的求眾數方法 - 摩爾投票演算法(演算法思想+求眾數的三種方法+摩爾投票演算法改進版求眾數 II)

木阿南二三記發表於2019-03-26

摩爾投票演算法是一種線上性時間O(n)和空間複雜度O(1)的情況下,在一個元素序列中查詢包含最多的元素的典型的流演算法。

下面用此演算法來解LeetCode的169. 求眾數、229. 求眾數 II。

一、求眾數:

給定一個大小為 n 的陣列,找到其中的眾數。眾數是指在陣列中出現次數大於 ⌊ n/2 ⌋ 的元素。
你可以假設陣列是非空的,並且給定的陣列總是存在眾數。

示例 1:
輸入: [3,2,3]
輸出: 3

示例 2:
輸入: [2,2,1,1,1,2,2]
輸出: 2

1.普通方法(二重迴圈):(此方法在資料量大時,必然消耗時間很大)

class Solution {
    public int majorityElement(int[] nums) {
    	int n = nums.length/2;
        for(int i=0; i<nums.length; i++){
            int count = 0;
            for(int j=0; j<nums.length; j++){
               if(nums[j] == nums[i]){
                   count++;
               }
            }
            if(count > n){
                return nums[i];
            }else{
                count = 0;
            }
        }
        return -1;
    }
}

2.排序法:(排序後眾數一定出現在索引為nums.length/2的位置上)

import java.util.Arrays;
class Solution {
    public int majorityElement(int[] nums) {
    	Arrays.sort(nums);
        return nums[nums.length/2];
    }
}

3.摩爾投票演算法:(時間複雜度為O(n),空間複雜度為O(1))

首先,陣列中出現次數大於 [n/2 ]的元素只可能有一個(假設這個數字在陣列中一定存在)。摩爾投票演算法是掃描整個陣列,每次從其中選擇兩個不相同的數字刪除掉,直到最後剩下一個數字或幾個相同的數字,就是出現次數大於總數一半的那個

(1)先定義一個基數(陣列中可以直接使用索引,下面程式中時i,)和一個計數器(count)。

(2)計數器基數為1,代表已經掃描過nums[0],從索引1的位置開始掃描整個陣列(也可以直接從頭開始),遇到與基數相同的數加1,遇到不同的數減1。

(3)當計數器為0時,資料被分為兩段,前一段資料中被計數的元素數和基數數量是相等的,而後面的資料又滿足詞頻最高的數大於總數一半的情形,因此後面的資料就成為了我們的下一個目標,前面的資料就被捨棄了。

(4)完畢之後索引i記錄的那個數就是出現次數最多的數。

class Solution {
    public int majorityElement(int[] nums) {
    	int i = 0;
    	int count = 1;
    	for(int j=1; j<nums.length-1; j++){
    		if(nums[i] == nums[j]){
    			count++;
    		}
    		else{
    			count--;
    			if(count == 0){
    				i = j+1;
    			}
    		}
    	}
        return nums[i];
    }
}

以上程式的測試類:

public class LeetcodeTest {
	public static void main(String[] args) {
		Solution So = new Solution();
		int[] nums = {2,2,1,1,1,2,2};
		System.out.println(So.majorityElement(nums));
	}
}

二、求眾數 II

給定一個大小為 的陣列,找出其中所有出現超過 ⌊ n/3 ⌋ 次的元素。

說明: 要求演算法的時間複雜度為 O(n),空間複雜度為 O(1)。

示例 1:
輸入: [3,2,3]
輸出: [3]

示例 2:
輸入: [1,1,1,3,3,2,2,2]
輸出: [1,2]

要求演算法的時間複雜度為 O(n),空間複雜度為 O(1),那麼這個題自然就是摩爾投票演算法的升級版

掃描整個陣列,每次從其中選擇三個不相同的數字刪除掉,最後留下的就是出現次數超過1/3的數。

(1)同樣先定義基數nums[i]、nums[j]和計數器count1,count2,這裡我們同樣可以推斷出在整組資料中出現次數超過 [ n/3 ]的數不多於兩個。

(2)掃描整個陣列,遇到與基數相同的數為對應計數器加1,遇到不同的數減1。

(3)當計數器count1或count2為0時,對應前面的資料段捨棄,以下一個數為基數繼續遍歷。

(4)完畢之後索引i、j記錄的數就是出現次數超過 [ n/3 ]的數。

import java.util.List;
import java.util.ArrayList;
class Solution {
    public List<Integer> majorityElement(int[] nums) {
        List<Integer> res = new ArrayList<Integer>();
        int i = 0; 
        int j = 1;
        int count1 = 0;
        int count2 = 0;
        for(int k=0; k<nums.length;k++){
            if(nums[k] == nums[i]){
                count1++;
            }else if(nums[k] == nums[j]){
                count2 ++;
            }else if(count1 == 0){
                i = k;
                count1 = 1;
            }else if(count2 == 0){
                j = k;
                count2 = 1;
            }else{
                count1--;
                count2--;
            }
        }
        //檢驗
        count1 = 0;
        count2 = 0;
        for(int k=0; k<nums.length; k++){
        	if(nums[k] == nums[i]){
        		count1++;
        	}else if(nums[k] == nums[j]){
        		count2++;
        	}
        }
        if(count1 > nums.length/3){
        	res.add(nums[i]);
        }
        if(count2 > nums.length/3){
        	res.add(nums[j]);
        }
        return res;
    }
}

測試類:

public class LeetcodeTest {
	public static void main(String[] args) {
		Solution So = new Solution();
		int[] nums = {1,1,1,3,3,2,2,2};
		List<Integer> res = So.majorityElement(nums);
		System.out.println(res);
	}
}

 

相關文章