1億條UUID中查詢重複次數最多的那一個(演算法)
阿新 • • 發佈:2022-04-22
1億條UUID中查詢重複次數最多的那一個(演算法)
涉及知識點:
- hashcode運用
- bitmap型別的資料格式
- 使用hash矩陣解決hash衝突
1,題目描述
有1億條UUID資料,裡面有重複的UUID,查找出重複次數最多的UUID
(同時記憶體限制1G)
2,解題思路
分析題目:
- UUID一般情況下是32為的String型別,佔用記憶體32*4位元組= 128位元組
- 如果直接使用hashMap來儲存肯定是記憶體超出限制 128*1億=11.9GB
- 那麼就需要使用其他方式進行出現次數的統計,直接想到了bitmap
- 直接使用bitmap也是不可取的,原因uuid轉換為具體的儲存位的時候,肯定會有hash衝突的情況出現
- 聯絡到我之前使用的過的一個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,參考連結
- Caffeine快取框架 中的Count-Min Sketch演算法