题解/算法 {6955. 长度递增组的最大数目}
@LOC: 1
LINK: https://leetcode.cn/problems/maximum-number-of-groups-with-increasing-length/
;
有[0,1,...n)
这些数 每个数有个cont
个数;
性质: 最多分n组;
因为到了n+1
组 他需要n+1
个不同的数;
如果可以分k组 则必然可以分1/2/.../k
组, 符合二分, 因此对K = [1,n]
进行二分;
@DELI;
#判断能否分成K组#
对所有个数进行逆序排序, 比如[9, 8, 5, 5, 3]
K=4
, 我们需要判断是否构成[4,3,2,1]
;
组1 [a1]
组2 [a2, b2]
... ...
组K [aK, bK, ... ]
K K-1 ..., 1
注意`a1,a2,...,aK` 不一定全都相同; (比如`[1,1,1] K=2`);
从前往后遍历, 对于9
(不用关心他对应的数到底为多少) 他要去满足4
, 还剩下了5个 (这个5个 就直接扔掉 肯定不会再用到) 因为这K个组里 都有9这个元素了已经;
.
也就是, 对于C: 当前数的个数, T: 需要的个数(对应上图中的*每一列[K, K-1, ...]*)
(如果C >= T
那么多余出的C - T
个 直接扔掉);
关键是看C < T
的情况; 此时你需要找其他元素来填充T-C
个;
#错误做法#
设置一个右指针r
(表示右侧个数最少的数), 用(个数最少的数)来填充;
错误, 比如[3,3,3,1] K=4
; 在(C=3, T=4)
时 你需要填充1个 如果用1来填充 则变成了[0,3,3,0] K=3
这是无解的, 因此你这种做法 会认定无解;
但是, 可以用第3个3来填充 变成[0,3,2,1] K=3
是可解的;
#正确但超时的贪心做法#
用heap来维护 选择最多的个数 来填充, 这个贪心 你可以想象成 个数最多的元素 他会有多余元素 而个数少的没有多余元素; 时间是N * logN * logN
会超时;
#正确做法#
其实, 你无需关注谁去填充 你可以让他变成操作后效性(后续才操作生效执行 账先记着 以后再算), 也就是 对于当前的C,T
需要填充T-C
个, 你可以不在当前这步 就必须要找出来 用来填充的元素, 而是用一个全局的delta
把他给记起来, 等到以后 人家有多余元素了 再补回来!
.
证明下: 以后元素的多余元素 补回来 是合法的 不会产生冲突的; C, ...x...
最开始的C 他还需要补T-C
个 (我们要证明 后来补的这T-C
个元素 不会与后面的列 产生重复 即同一组有重复元素); 因此C >= x
, 假如x有多余元素 我们将其多余元素 放到C的顶部, 这样 这些多余元素 与当前的x 肯定不在同一组内; 画图更形象:
[x
[x
组K-1: [C ...
组K: [C ... x
T
当前的x 是和C同组的, 多余的x 是在C的*上面*, 这样就错开了;
也就是, 一个元素有多余的元素, 他只能往前去填充, 不可以往后填充;
比如[4,1,1] K=3
, 假如你让4往后填充1个 变成[3,2,1]
这就冲突了, 因为新填充的位置高度 一定小于当前列的高度, 因此就冲突重复了(一定有某个组里有两个4);
而往前填充, 由于数组是递减的 我们可以填充到前面的顶部 他的高度 肯定高于当前高度, 比如[2,2,2] K=3
可以往前填充;
MARK: @LOC_0
;
对于前K个元素, 第i个元素 一定是在第i列 (如果多余 再补到前面列)
即[a1, a2, .., aK, bi]
这K个组的K个列 就建立在a1,a2...,aK
, 准确的说 第i列的地基 就是ai
(要么ai < 所需高度
会使用后面的元素来填充; 要么ai >= 所需高度
则多余出来的ai
会去填充前面的列);
对于bi
都用来去填充前面的列;
疑问
根据LINK: @LOC_0
, 如何证明bi
补到前面不会冲突呢?
bi
bi bi
A1 A2 A3 ... AK
会出现这种情况吗?
提示: 如果AK = 1
那么bi <= 1
而此时是3; 否则AK > 1
那么AK
会先去弥补 轮不到bi; 因此, 考虑bi
就是A3 因为A3 <= A2
因此A3最多多余出1个 而不是此时的3个;
算法
即遍历K,K-1,...1
一共是K次, 但是 你可能认为就是K个循环 也就是只遍历数组的前K个元素 这是错误的!
我们填充, 是会用到全体元素的 而不是前K个; 比如[2,2,1,1] K=3
假如你只用前K个元素 是无解的, 需要最后那个1 把他填充到[3,2,1,0]
;
而遍历组的高度 确实是K次循环K,...1
, 因此 一个是K循环 一个是N循环, 很容易犯错; 你可以将组的高度 也设置成N次循环 即K...1 0...0
后面都设置为0! 这非常巧妙, 因为本来后来的元素 就是去填充前面的K列 你可以认为他要满足一个高度为0的列;
int maxIncreasingGroups(vector<int>& A) {
int n = A.size();
sort( A.begin(), A.end(), greater<>());
{ // 整数二分
auto __check_success = [&]( int _mid) -> bool{
long long delta = 0;
auto tar = _mid + 1;
for( int i = 0; i < n; ++i){
-- tar; if( tar < 0) tar = 0;
if( A[ i] >= tar){
delta += A[i] - tar;
Tools::Min_self( delta, 0LL);
}
else{
delta += A[ i] - tar;
}
}
return delta == 0;
};
#define __ANSWER_IN_LEFT_ 1 // 如果`1` 对应为`l=mid`, 否则要定义为`0` 即`r=mid`;
int __l = 1, __r = n;
ASSERT_( __l <= __r);
int __mid;
while( __l < __r){
__mid = (__l + __r + __ANSWER_IN_LEFT_) >> 1;
if( __check_success( __mid))
#if __ANSWER_IN_LEFT_
__l = __mid;
#else
__r = __mid;
#endif
else
#if __ANSWER_IN_LEFT_
__r = __mid - 1;
#else
__l = __mid + 1;
#endif
}
ASSERT_( __l == __r);
return __l;
if( __check_success( __l)){
return __l;
}
else{
}
#undef __ANSWER_IN_LEFT_
} // 整数二分
ASSERT_( 0);
}