【问题标题】:A linear/logarithmic algorithm to find the minimum element in array after some operations on the array一种线性/对数算法,用于在对数组进行一些操作后找到数组中的最小元素
【发布时间】:2017-03-12 04:26:55
【问题描述】:

问题如下:
给定一个大小为n 的数组,我们可以对其执行k 操作。在每个操作中,任何元素都可以分为ab两个数字,这样(a+b)是前一个元素,新的元素个数是n+1。那么作为数组的最大元素,我们能得到的最小数是多少呢?
例如:
假设n=3k=2
数组由: 3 3 4
一次操作后:
3 3 2 2
第二次手术后:
3 1 2 2 2
因此答案是3,因为它是数组最大值的可能最小值。
我试图为此想出一个算法,但每个算法都是O(n^2)。我正在考虑检查每个数字是否有可能直到数组的最大元素=>n^2。 我想到的算法是将i=1 带到i=k,在那里我检查i 的解决方案是否可能。当每个大于i 的数字被分成组时,i 是可能的。例如,取一个数字xx可以分为x/i一组i和另一组x%i(如果!=0)。如果没有。组是y,我们需要y-1 操作。我得到的第一个i 就是答案。
有没有更好的算法可以解决这个问题。

【问题讨论】:

  • 据我所知,逻辑是找出数组中的最大数,如果不是第一个,则将其拆分(偶数:除以二;奇数:除乘二并在左边加一 (a) 而不是 b) 并继续重复该过程直到迭代 k 次。
  • @KaushikNP 在这种情况下复杂度是O(knlogn)
  • 哦,对不起,我当时在想python。为了找到最大元素本身,在 C 中出现了另一个循环。所以是的,不会成功。
  • @KaushikNP:即使拆分也不总是最佳的。例如,如果您使用初始数组 {6} 和 2 个拆分进行偶数拆分,则第一个拆分为 {3, 3},第二个拆分为 {3, 2, 1},但最佳结果是 {2, 2, 2}
  • @user2357112....正是....这正是我每次都得到n^2的复杂性的原因

标签: c++ algorithm time-complexity


【解决方案1】:

对输入数组进行计数排序。
为 k 个最大数字中的每一个分配 1 次操作(如果输入中出现 x 次,则为其分配 x 次操作)。
将指针 A 设置为未分配任何操作的最大数。
记录可重新分配操作的数量,最初为零。
从高到低检查数字,当你发现一个数字不能使用分配给它的操作(或添加可重新分配的操作后)分成小于 A 的部分时:
- 将 A 移动到下一个数字
- 删除 A 的已分配操作并将它们添加到可重新分配操作的计数中。
- 再次从列表末尾开始检查。

(当然还有一些繁琐的细节需要解决。)

这为计数排序和存储每个数字的操作次数使用了额外的空间,但是不同步骤的时间复杂度类似于 O(n)、O(k) 和 O(k^2),但是我不是计算复杂性的专家。 (也许用于计数排序的 O(n) 是我在 javascript 中使用稀疏关联数组得到的一种错觉)。

【讨论】:

    【解决方案2】:
    1. 在数组中找出 k+1 个最大数:复杂度 O(n log k*)。将它们转换为有理数格式:附加空间 O(k*)。
    2. 制作一堆有理数:复杂度 O(k*)。
    3. 增加堆中最大数的分母,k 倍:复杂度 O(k log k*)。
    4. 结果位于堆顶。向上舍入到下一个整数。

    总成本:时间O((n+k) log k*),空间O(k*),其中 k* = min(n, k).

    struct rational {
        int num;
        int denom;
    
        rational( int in_num = 0, int in_denom = 1 )
            : num{ in_num }, denom{ in_denom } {}
    };
    
    bool operator < ( rational lhs, rational rhs )
        { return static_cast<long>( lhs.num ) * rhs.denom < static_cast<long>( rhs.num ) * lhs.denom; }
    bool operator > ( rational lhs, rational rhs )
        { return rhs < lhs; }
    
    int max_after_divisions( std::vector< int > const & in, std::size_t k ) {
        std::size_t n = std::min( in.size(), k + 1 );
        std::vector< rational > v( n );
        std::partial_sort_copy( in.begin(), in.end(), v.begin(), v.end(), std::greater<>{} );
        std::make_heap( v.begin(), v.end() );
        while ( k -- ) {
            std::pop_heap( v.begin(), v.end() );
            ++ v.back().denom;
            std::push_heap( v.begin(), v.end() );
        }
        return ( v.front().num + v.front().denom - 1 ) / v.front().denom;
    }
    

    【讨论】:

    • (1) 不必对整个数组进行排序,只对最大的k个元素进行排序(复杂度n log k)。 (2)n为什么要参与计算?你会如何处理只包含一个数字但它是 2^256 的数组?
    • @n.m. 1) 好点,std::partial_sort。 2)这就是第二次二进制搜索的目的。此外,与此答案不同,无论如何都需要完成。不过,将这两个搜索分别编码并避免重复遍历数组,还是要简单一些。
    • 对不起,我的意思是 m,而不是 n。我认为您不需要搜索数字空间,因此不应涉及 m 或 log m 。尝试解决输入为有理数的相同问题。
    • @n.m.有趣的。你似乎对这个问题有些熟悉。还有你的名字,n.m. :) 。听起来我使用堆的第一种方法更好,除了不是执行贪婪的整数除以二,堆应该存储有理数。
    • 起初我认为有理数是一种花哨的数学技巧,但它们只是对数字进行运算的一种方式,不是吗?
    猜你喜欢
    • 2019-09-04
    • 1970-01-01
    • 2020-06-24
    • 1970-01-01
    • 2020-05-02
    • 2012-03-16
    • 1970-01-01
    • 2013-03-07
    • 1970-01-01
    相关资源
    最近更新 更多