題解/算法 {2910. 合法分组的最少组数}
@LINK: https://leetcode.cn/problems/minimum-number-of-groups-to-create-a-valid-assignment/
;
令B數組為 A數組裡每個數出現的次數, 則答案為: 將B數組拆分為B1數組 且B1數組所有數都在[K-1, K]
範圍, 求B1的最小長度;
.
比如A={5,6,6,7,7,7}
, 則B={1,2,3}
, 讓B1={1,2, 1,2}
B1的最小長度為4;
1: 枚舉B1的長度cont
判斷他是否是答案;
.
#錯誤#: 你可能認為這個cont
是符合單調性的, 其實這是錯誤的, 即如果可以拆分成cont
個 那麼也就可以拆封成>cont
個; 這是錯誤的, 因為你並沒有明確的證據證實這一點, 比如A={7,8}
他可以拆分為cont=4
即{3,4,4,4}
, 但是拆分不成cont=5
; 因此二分是錯誤的, 這裡對cont
的枚舉 就是直接暴力;
2: 判斷B數組是否可以拆分為一個長度為cont
的B1數組; 注意這裡的定義 不是拆分成<=cont
長度, 這裡不是二分;
.
令Sum: B的和
, 假如是有解的 設最終B1數組所有元素是在[K-1, K]
範圍內, @IF(Sum%cont==0
):[令avg=Sum/cont
, B1數組一定形如[avg,...,avg]
(一定不存在!=avg
的元素) 此時讓K=avg/avg+1
都可以]–@ELSE:[令avg=Sum/cont
, 則B1一定會形如[(avg+1)...(avg+1), avg,...,avg]
, 此時讓K=avg+1
];
.
對弈B的每個元素c, 求將c分解為[K-1,K]
範圍內的最小個數, 比如c=7, K=3, 答案為[2,2,3]
;
.
#錯誤#: 最終一定是(最多有1個K-1
) 因為2個K-1
他可以合併出一個K出來; 這是錯誤的, 因為雖然2個K-1
可以合併出一個K 但是他的餘數 就不是[K-1,K]
範圍的了, 比如說[2,2,3]
你把他變成[1,3,3]
就錯誤了;
.
這個問題 確實需要思維跳躍性, 我們肯定是盡可能的分解出K來, 因為他越多 那麼cont
就會越少; 將c分解成[K,...,K, (r)]
的形式(r是餘數 前面有m=c/K
個K), 當r>0
時 我們要將他變成r -> (K-1)
他需要增加K-1-r
個1 他們可以從前面m
個K裡面去除掉, 因此判斷是否m >= (K-1-r)
;
以上還是挺複雜的… 你可以把枚舉cont
變成遞減的枚舉K
(K越大 cont
越小), 但此時K也是不滿足二分的, 這樣你就不需要分析上門那個avg
;
你可能認為時間是O(N^2)
所以會想到二分… 首先這個題不符合二分 (因為上面判斷的是 是否可以精確的劃分為cont
個 而不是劃分為<=cont
, 而且算法裡用到取模操作 可能在cont
是整除的 而在cont+1
就不是整除的 他並沒有展示出明顯的單調性 所以二分是錯誤的), 而且其實是時間不是N^2
, 因為當B長度很大時 他的元素數值會很小, 而當他元素數值很大時 B的數組又會很小;
int minGroupsForValidAssignment(vector<int>& A) {
unordered_map<int,int> Mp;
int Ma = 0;
for( auto i : A){ Mp[ i] ++; MaxSelf_( Ma, Mp[i]);}
int Sum = 0;
for( auto & [a,b] : Mp){ Sum += b;}
auto Check = [&]( int _cont){
int K = (Sum + (_cont - 1)) / _cont;
int cont = 0;
for( auto & [t, c] : Mp){
auto c1 = c / K;
auto rest = c % K;
if( rest == 0){ cont += c1;}
else if( (K - 1 - rest) <= c1){ cont += (c1 + 1);}
else{ return false;}
if( cont > _cont){
return false;
}
}
return true;
};
for( int ANS = Mp.size();; ++ANS){
if( Check( ANS)){
return ANS;
}
}
ASSERT_(0);
}