题解/算法 {6955. 长度递增组的最大数目}

题解/算法 {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);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值