题解/算法 {3134. 找出唯一性数组的中位数}
@LINK: https://leetcode.cn/problems/find-the-median-of-the-uniqueness-array/
;
令Count[l,r]
为 A[l....r]
里 不同元素的个数; 令答案数组是ANS[]
(他的长度是(1+N)*N/2
), 我们的目的是 求ANS的中位数;
看一个有点关联性的题目: @LINK: https://editor.csdn.net/md/?articleId=134148990
, 这很容易给你带入到一个错误的思路里…
错误做法: 我们得到ANS
, 或者说 得到x
在ANS中出现的次数也可以, 总之就是得到ANS; 这是一个很自然(暴力)的做法;
假如说 以A[l]
开头的 形如A[l, >=l]
的子数组 他的Count值依次为: [1, 1, 2, 2, 3, 3, 4, 4, ...]
; 然后我们要更新到 以A[l+1]
开头的 形如A[l+1, >=(l+1)]
的子数组 他的Count值 是什么呢? 假如说与A[l]
相同的 且最靠左的是A[ll]
, 那么从A[l]
变成A[l+1]
会导致 Count[l, l+1, l+2, ..., ll-1]
这些值 都-=1
, 其他的Count[>=ll]
的值 不变;
.
于是你想到了线段树 维护区间修改, 可是还有一个问题, 你即使得到了Count[l, ...N-1]
的值 你怎么记录到答案里面呢? 即怎么把他们放到ANS里面呢? 一个个扫描 肯定不行, 但又别无他法…
其实, 这个思路是行不通的…
正解是二分, 中位数 是满足单调性的;
长度为奇数: 排序后的A=[ M个数, x, M个数] (m为中位数) 则所有`<x`的数一定有`<=M`个;
长度为偶数: 排序后的A=[ M个数, x, M+1个数] (m为中位数) 则所有`<x`的数一定有`<=M`个;
反证很容易, 如果`<x`的数 有`>M`个 那么他就占据了`中位数的位置了`, 因此此时x肯定不是中位数;
因此 问题转换为: 求Count值<x
的 子数组 有多少个;
这可以用双指针, 比如说 对于L, 他最大的是R (此时Count[L...R] < x
且Count[L...(R+1)] >= x
), 那么 他就贡献了R-L+1
个子数组 (这些子数组的Count值是<x
的);
看一个错误: 如果A[R] != A[R+1]
, 那么Count[L...R] != Count[L...(R+1)]
; 很容易犯的直觉错误… (如果说A[R]==A[R+1]
此时确实有Count[L-R] == Count[L-(R+1)]
);
.
比如[1,....,2, 1]
, 虽然2!=1
, 但显然Count[1....2] == Count[1...2,1]
; 因此你需要用map
来维护A[L...R]
判断A[R+1]
是否在map里面;
int medianOfUniquenessArray(vector<int>& A) {
int N = A.size();
auto all = (1 + N) *1LL* N / 2;
int64_t M = all / 2;
if( all % 2 == 0){ -- M;}
{ // ___BinarySearch
auto Check = [&]( int _mid)->bool{ // `<mid` 有 `<= M`个;
if( _mid == 1){ return 0 <= M;}
unordered_map<int,int> Cont;
int count = 0;
auto Modify = [&]( int _k, bool _a)->void{
if( _a){ ++ Cont[_k]; if( Cont[_k] == 1){ ++ count;}}
else{ -- Cont[_k]; if( Cont[_k] == 0){ -- count;}}
};
int64_t sum = 0;
Modify( A[0], 1); // `l==r==0`;
for( int l = 0, r = l; l < N; ++l){
while( r+1 < N){
//> 写成`count + (A[r]==A[r+1] ? 0:1)`是错的...
if( count + (Cont[A[r+1]]>0 ? 0:1) < _mid){ ++ r; Modify(A[r], 1);}
else{ break;}
}
sum += (r - l + 1);
if( sum > M){ return 0;}
Modify( A[l], 0);
}
return 1;
};
int l = 1, r = N;
ASSERTsystem_( l <= r);
while( l < r){
auto mid = (l+r+1) >> 1;
if( Check( mid)){ l = mid;}
else{ r = mid - 1;}
}
if( Check(l)){
return l;
}
else{
}
} // ___BinarySearch
ASSERTcustom_(0);
return -1;
}