1. 程式人生 > 其它 >1億條UUID中查詢重複次數最多的那一個(演算法)

1億條UUID中查詢重複次數最多的那一個(演算法)

1億條UUID中查詢重複次數最多的那一個(演算法)

涉及知識點:

  1. hashcode運用
  2. bitmap型別的資料格式
  3. 使用hash矩陣解決hash衝突

1,題目描述

有1億條UUID資料,裡面有重複的UUID,查找出重複次數最多的UUID

(同時記憶體限制1G)

2,解題思路

分析題目:

  1. UUID一般情況下是32為的String型別,佔用記憶體32*4位元組= 128位元組
    • 如果直接使用hashMap來儲存肯定是記憶體超出限制 128*1億=11.9GB
    • 那麼就需要使用其他方式進行出現次數的統計,直接想到了bitmap
  2. 直接使用bitmap也是不可取的,原因uuid轉換為具體的儲存位的時候,肯定會有hash衝突的情況出現
  3. 聯絡到我之前使用的過的一個countMinSketch演算法中的hash矩陣,該方法可以很好的解決hash衝突問題,同時耗費記憶體也少

3,程式碼實現

public class Solution1 {

    /*
     * 題目描述:
     * 資料流中有1億條UUID資料,查找出其中重複次數最多的UUID,記憶體1G
     *
     * 複雜度分析:
     * 時間複雜度O(N) 只需要進行1次遍歷
     * 空間複雜度O(N) 為了防止hash衝突需要與你的資料量相匹配
     *
     * 解題思路:
     * ① 要利用類似bitmap這種資料型別進行歷史資料的統計
     * ② 同時使用hash函式來進行儲存位的確定
     * ③ 使用hash矩陣來避免hash衝突
     *
     * 優點:佔用記憶體小
     * 缺點:
     *      當資料量特別大的時候容易出現hash衝突,我這邊使用多維的hash矩陣就是為了解決hash衝突問題
     *      但是資料量進一步變大,還是可能出現hash衝突的情況,解決辦法就是要相對的調整hash矩陣的大小進行匹配
     * 改進點:
     * ① 如果出現的最大頻次的範圍有明確資訊的話,可以修改hash矩陣的資料型別由4位元組的int改為2位元組的short型別,用以節省記憶體耗費
     * ② 這種解法極端情況下,還是會出現hash衝突的問題,除非針對hash矩陣擴容
     */
    public String findMaxCountUuid(String[] strings) {
        int countMatrixArrangeLen = 10000000;
        int[][] countMatrix = new int[countMatrixArrangeLen][3];// 只佔用114MB記憶體
        int maxCount = 0;
        String maxCountStr = "";

        // 遍歷:進行計數,同時記錄最大重複次數和最大重複次數的UUID
        for (String string : strings) {
            // 3種hash函式用於定位UUID儲存的資料位標
            int hashIndex1 = string.hashCode() % countMatrixArrangeLen;
            int hashIndex2 = string.hashCode() * 17 % countMatrixArrangeLen;// 直接乘以質數,這個算是一種比較簡單的改動
            int hashIndex3 = string.hashCode() / 17 % countMatrixArrangeLen;// 直接除以質數

            countMatrix[hashIndex1][0]++;
            countMatrix[hashIndex2][1]++;
            countMatrix[hashIndex3][2]++;

            // 獲取當前UUID的出現次數
            int curCount = Math.min(Math.min(countMatrix[hashIndex1][0], countMatrix[hashIndex2][1]), countMatrix[hashIndex3][2]);
            if (maxCount < curCount) {
                // 更新出現次數最多的UUID記錄
                maxCount = curCount;
                maxCountStr = string;
            }
        }
        return maxCountStr;
    }

    public static void main(String[] args) {

        String[] strings = {"1", "1", "1", "2", "3"};
//        String[] strings = {"1"};
        Solution1 solution = new Solution1();
        System.out.println(solution.findMaxCountUuid(strings));
        System.out.println();
    }
}

4,解題分析

複雜度分析:

  • 時間複雜度O(N) 只需要進行1次遍歷
  • 空間複雜度O(N) 為了防止hash衝突需要與你的資料量相匹配

​ 優點:

  • 佔用記憶體小,實現比較方便
  • 時間複雜度、空間複雜度比較低

​ 缺點:

  • 當資料量特別大的時候容易出現hash衝突,我這邊使用多維的hash矩陣就是為了解決hash衝突問題

  • 但是資料量進一步變大,還是可能出現hash衝突的情況,解決辦法就是要相對的調整hash矩陣的大小進行匹配

    改進點:

  • ① 如果出現的最大頻次的範圍有明確資訊的話,可以修改hash矩陣的資料型別由4位元組的int改為2位元組的short型別,用以節省記憶體耗費

  • ② 這種解法極端情況下,還是會出現hash衝突的問題,除非針對hash矩陣擴容

5,拓展

如果需要查詢所有出現次數最多的UUID(就是說有可能是多個UUID都出現最大次數)

這裡的第二次遍歷時,直接將出現次數等於maxCount的UUID加一個Set中即可

    public String[] findMaxCountUuid1(String[] strings) {
        int countMatrixArrangeLen = 10000000;
        int[][] countMatrix = new int[countMatrixArrangeLen][3];// 只佔用114MB記憶體
        int maxCount = 0;
        HashSet<String> hashSet = new HashSet<>();

        // 遍歷:進行計數,同時記錄最大重複次數和最大重複次數的UUID
        for (String string : strings) {
            // 3種hash函式用於定位UUID儲存的資料位標
            int hashIndex1 = string.hashCode() % countMatrixArrangeLen;
            int hashIndex2 = string.hashCode() * 17 % countMatrixArrangeLen;// 直接乘以質數,這個算是一種比較簡單的改動
            int hashIndex3 = string.hashCode() / 17 % countMatrixArrangeLen;// 直接除以質數

            countMatrix[hashIndex1][0]++;
            countMatrix[hashIndex2][1]++;
            countMatrix[hashIndex3][2]++;

            // 獲取當前UUID的出現次數
            int curCount = Math.min(Math.min(countMatrix[hashIndex1][0], countMatrix[hashIndex2][1]), countMatrix[hashIndex3][2]);
            if (maxCount < curCount) {
                // 更新出現次數最多的UUID記錄
                maxCount = curCount;
                hashSet.clear();
                hashSet.add(string);
            }else if(maxCount == curCount){
                hashSet.add(string);
            }
        }
        return hashSet.toArray(new String[0]);
    }

但是按照上面的做法也是有風險的,因為例如統計到maxCount=10的時候,有5w+個UUID那麼記憶體會爆掉

相對更保險的方式:遍歷2次的方案

    public String[] findMaxCountUuid2(String[] strings) {
        int countMatrixArrangeLen = 10000000;
        int[][] countMatrix = new int[countMatrixArrangeLen][3];// 只佔用114MB記憶體
        int maxCount = 0;
        HashSet<String> hashSet = new HashSet<>();

        // 第一次遍歷:進行計數,同時記錄最大重複次數
        for (String string : strings) {
            // 3種hash函式用於定位UUID儲存的資料位標
            int hashIndex1 = string.hashCode() % countMatrixArrangeLen;
            int hashIndex2 = string.hashCode() * 17 % countMatrixArrangeLen;// 直接乘以質數,這個算是一種比較簡單的改動
            int hashIndex3 = string.hashCode() / 17 % countMatrixArrangeLen;// 直接除以質數

            countMatrix[hashIndex1][0]++;
            countMatrix[hashIndex2][1]++;
            countMatrix[hashIndex3][2]++;

            // 獲取當前UUID的出現次數
            int curCount = Math.min(Math.min(countMatrix[hashIndex1][0], countMatrix[hashIndex2][1]), countMatrix[hashIndex3][2]);
            if (maxCount < curCount) {
                // 更新出現次數最多的UUID記錄
                maxCount = curCount;
                hashSet.clear();
                hashSet.add(string);
            }else if(maxCount == curCount){
                hashSet.add(string);
            }
        }

        // 第二次遍歷:查找出現次數為maxCount的UUID
        for (String string : strings) {
            // 3種hash函式用於定位UUID儲存的資料位標
            int hashIndex1 = string.hashCode() % countMatrixArrangeLen;
            int hashIndex2 = string.hashCode() * 17 % countMatrixArrangeLen;// 直接乘以質數,這個算是一種比較簡單的改動
            int hashIndex3 = string.hashCode() / 17 % countMatrixArrangeLen;// 直接除以質數

            // 獲取當前UUID的出現次數
            int curCount = Math.min(Math.min(countMatrix[hashIndex1][0], countMatrix[hashIndex2][1]), countMatrix[hashIndex3][2]);

            if(maxCount == curCount){
                hashSet.add(string);
            }
        }

        return hashSet.toArray(new String[0]);
    }

6,參考連結

  1. Caffeine快取框架 中的Count-Min Sketch演算法