最小化操作次数使数组的所有元素相等

Posted

技术标签:

【中文标题】最小化操作次数使数组的所有元素相等【英文标题】:Minimize the number of operation to make all elements of array equal 【发布时间】:2016-02-13 13:03:52 【问题描述】:

给定一个包含 n 个元素的数组,您只能执行 2 种操作来使数组的所有元素相等。

    将任意元素乘以 2 将元素除以 2(整数除法)

你的任务是最小化上述操作的总数,以使数组的所有元素相等。

例子

array = [3,6,7] 最小运算是 2,因为 6 和 7 可以除以 2 得到 3。

我什至想不出蛮力解决方案。

约束 1

【问题讨论】:

可能会提到约束 别忘了发布约束,这很重要! 蛮力也可以很快 【参考方案1】:
    通过二进制扩展将所有数字视为01 的字符串。

例如:3, 6, 7 分别表示为 11, 110, 111

    除以2相当于去掉最右边的01,乘以2相当于从右边加上一个0

对于由01 组成的字符串,让我们将它的“头”定义为一个子字符串,该子字符串是该字符串的左几项,以1 结尾。 例如:1100101 有头像111110011100101

    任务变为查找所有给定字符串中最长的公共头部,然后确定在该公共头部之后添加多少个0

一个例子:

假设您有以下字符串:

10101001101011101111010001

    找出10101001101011的最长公共头,即10101; 找出1010110111的最长公共头,即101; 找出1011010001的最长公共头,即101

那么你确定所有的数字都应该变成101 00...形式的数字。

要确定在101 之后要添加多少个0,请在每个字符串中找到紧跟101 的连续0 的数量:

对于10101001:1

对于101011:1

对于10111:0

对于1010001:3

仍然需要找到一个整数 k 来最小化 |k - 1| + |k - 1| + |k - 0| + |k - 3|。在这里我们找到k = 1。所以每个数字最后都应该是1010

【讨论】:

是的,这是正确的解决方案。一旦你观察到解的形状(前缀后跟零),这真的很容易。 @WhatsUp 如何有效地找到k? 鉴于您的限制,甚至可以通过暴力破解:尝试从020 的每个k,比如说。 我已经用这个实现替换了我的答案。我已经通过蛮力做到了,如果这还不够快,那么您可以在前缀之后将每个数字 0 的数字分组,因为这是唯一重要的事情并将其转化为一个因素。 这个答案是否符合问题的要求?只允许两种类型的操作;乘以二除以二。二进制展开将需要更多操作,并且诸如加法或减法之类的额外计算也将超出初始问题要求的范围。结果是正确的,但旅程似乎越界了。也许我误读了这个问题?【参考方案2】:

正如另一个答案所解释的,不需要回溯。为了好玩,对这种方法进行了一点实现。 (见底部在线运行链接):

首先我们需要一个函数来确定数字中的二进制位数:

    def getLength(i: Int): Int = 
         @annotation.tailrec
         def rec(i: Int, result: Int): Int =
           if(i > 0)
             rec(i >> 1, result + 1)
           else
             result
         rec(i, 0)
   

那么我们需要一个函数来判断两个等长数的公共前缀

   @annotation.tailrec
   def getPrefix(i: Int, j: Int): Int =
         if(i == j) i
         else getPrefix(i >> 1, j >> 1)

一个任意数字的列表:

   def getPrefix(is: List[Int]): Int = is.reduce((x,y) => 
         val shift = Math.abs(getLength(x) - getLength(y))
         val x2 = Math.max(x,y)
         val y2 = Math.min(x,y)
         getPrefix((x2 >> shift), y2)
   )

那么我们需要后缀的长度而不计算后缀的零:

    def getSuffixLength(i: Int, prefix: Int) = 
         val suffix = i ^ (prefix << (getLength(i) - getLength(prefix)))
         getLength(suffix)
    

现在我们可以计算将操作 i 与附加“零”零的前缀同步所需的操作数。

    def getOperations(i: Int, prefix: Int, zeros: Int): Int = 
         val length = getLength(i) - getLength(prefix)
         val suffixLength = getSuffixLength(i, prefix) 
         suffixLength + Math.abs(zeros - length + suffixLength)
    

现在我们可以找到最少的操作数并将其与我们将同步到的值一起返回:

    def getMinOperations(is: List[Int]) = 
        val prefix = getPrefix(is)
        val maxZeros = getLength(is.max) - getLength(prefix)
        (0 to maxZeros).mapzeros => (is.mapgetOperations(_, prefix, zeros).sum, prefix << zeros).minBy(_._1)
    

您可以在以下位置尝试此解决方案:

http://goo.gl/lLr5jl

找到正确数量的零的最后一步可以改进,因为只有没有前导零的后缀的长度很重要,而不是它的样子。所以我们可以通过计算有多少来计算我们需要的操作数:

    def getSuffixLength(i: Int, prefix: Int) = 
         val suffix = i ^ (prefix << (getLength(i) - getLength(prefix)))
         getLength(suffix)
    

    def getMinOperations(is: List[Int]) = 
        val prefix = getPrefix(is)
        val maxZeros = getLength(is.max) - getLength(prefix)
        val baseCosts = is.map(getSuffixLength(_,prefix)).sum
        val suffixLengths: List[(Int, Int)] = is.foldLeft(Map[Int, Int]())
            case (m,i) => 
                val x = getSuffixLength(i,prefix) - getLength(i) + getLength(prefix)
                m.updated(x, 1 + m.getOrElse(x, 0))
            
        .toList
        val (minOp, minSol) = (0 to maxZeros).mapzeros => (suffixLengths.map
           case (x, count) => count * Math.abs(zeros + x)
        .sum, prefix << zeros).minBy(_._1)
        (minOp + baseCosts, minSol)
    

所有腋窝手术只需要最大数量大小的对数时间。我们必须通过孔列表来收集后缀长度。然后我们必须猜测在最大数量的许多零中最多有对数的零的数量。所以我们应该有一个复杂度

O(|list|*ld(maxNum) + (ld(maxNum))^2)

所以对于你的界限,这基本上是输入大小的线性。

这个版本可以在这里找到:

http://goo.gl/ijzYik

【讨论】:

以上是关于最小化操作次数使数组的所有元素相等的主要内容,如果未能解决你的问题,请参考以下文章

《LeetCode之每日一题》:183.最小操作次数使数组元素相等

Leetcode——最小操作次数使数组元素相等

Leetcode——最小操作次数使数组元素相等

一道题,最小操作次数使数组元素相等引发的思考

一道题,最小操作次数使数组元素相等引发的思考

一道题,最小操作次数使数组元素相等引发的思考