仅使用两个操作将数组转换为已排序的数组

时间:2012-01-19 14:49:37

标签: algorithm sorting

我在网上论坛上发现了这个问题:对如何解决这个问题非常感兴趣:

给出正整数的数组A.以最低成本将其转换为排序数组。唯一有效的操作是:
1)减少成本= 1
2)使用cost = value of element

从数组中完全删除元素

这是一家面向科技公司的面试问题

3 个答案:

答案 0 :(得分:11)

注意:原来的答案已被替换为我更有信心的答案(我也可以解释)。这两个答案在我的一组测试用例中产生了相同的结果。

您可以使用动态编程方法解决此问题。关键的观察是将数字减少到原始数组中找不到的值是没有意义的。 (非正式证明:假设您将数字O1减少到不在原始序列中的值X,以避免从结果序列中删除数字O2 > X。然后您可以相反,将O1减少为O2,并将费用降低O2-X)。

现在解决方案变得易于理解:它是一个二维的DP。如果我们将原始序列d的不同元素的元素排序为已排序的数组s,则d的长度将成为DP的第一个维度; s的长度成为第二个维度。

我们宣布dp[d.Length,s.Length] dp[i,j]的值是解决子问题d[0 to i]的成本,同时将解决方案的最后一个元素保持在s[j]之下。注意:此费用包括在d[i]小于s[j]时取消dp[0,j]的费用。

第一行d[0]计算为修剪s[j]d[0] < s[j]的费用,如果dp[i,j]则为零。 dp[i-1, 0 to j] + trim下一行的值计算为trim的最小值,其中d[i]是修剪s[j]d[i]s[j]的费用如果需要删除它,因为d[i]大于dp[d.Length-1, 0 to s.Length]

答案计算为最后一行static int Cost(int[] d) { var s = d.Distinct().OrderBy(v => v).ToArray(); var dp = new int[d.Length,s.Length]; for (var j = 0 ; j != s.Length ; j++) { dp[0, j] = Math.Max(d[0] - s[j], 0); } for (var i = 1; i != d.Length; i++) { for (var j = 0 ; j != s.Length ; j++) { dp[i, j] = int.MaxValue; var trim = d[i] - s[j]; if (trim < 0) { trim = d[i]; } dp[i, j] = int.MaxValue; for (var k = j ; k >= 0 ; k--) { dp[i, j] = Math.Min(dp[i, j], dp[i - 1, k] + trim); } } } var best = int.MaxValue; for (var j = 0 ; j != s.Length ; j++) { best = Math.Min(best, dp[d.Length - 1, j]); } return best; } 的最小值。

以下是C#中的实现:

O(N^2)

此直接实现的空间复杂度为O(N)。您可以通过观察同时仅使用最后两行来将其减少到{{1}}。

答案 1 :(得分:1)

考虑到允许操作的性质,我假设“排序”表示数组开头的最小值。

两个操作之间的性能边界发生在删除无序元素的成本等于将所有更高值元素递减到包括违规者的成本,或者删除所有较低值元素之后的成本。罪犯。您可以根据违规元素失序的原因选择递减前面的元素或删除后面的元素。如果它小于前一个元素,请考虑减少前面的元素;如果它大于下一个元素,请考虑删除后面的元素。

一些例子:

10 1 2 3 4 5

减少10比1,花费9。

1 2 3 4 10 4

删除4,费用为4.

1 2 3 4 10 5

删除5或减少10到5,花费5。

5 6 7 8 1 10

删除1,费用为1。

5 6 7 8 6 10

减少7和8到6,费用为3.

2 1 1 4 2 4 4 3

减少前1个,前4个减2个,另外两个减4个,费用为5个。

找到解决方案的最简单实现依赖于设置知识,因此效率非常低。值得庆幸的是,这个问题并不关心。我们的想法是遍历数组,并在遇到失序元素时决定是否删除或减少修复集合。更有效的实现方法是使用运行总计(而不是计算方法)并将数组向前和向后两次走。我写了一个更简单版本的模拟,因为我觉得它更容易阅读。

伪代码,返回总费用:

if array.Length < 2 : return 0; // no sorting necessary

resultArray = array.Copy();
int cost = 0;

for i = 0 to array.Length - 1 :

if i > 0 and array[i-1] > array[i] :

if CostToDecrementPreviousItems(i, array[i]) > array[i]) :
resultArray[i] = -1;
cost += array[i];
else :
cost += DecrementItemsThroughIndexGreaterThanValue(resultArray, i, array[i]);
end if

else if i < array.Length - 1 and array[i+1] < array[i] :

if CostToRemoveLaterItems(i, array[i]) > array[i] :
resultArray[i] = -1;
cost += array[i];
else :
cost += RemoveItemsAfterIndexGreaterThanValue(resultArray, i, array[i]);
end if

end if
end for

RemoveNegativeElements(resultArray);
array = resultArray;

return cost;

希望未定义的方法调用是自解释的。

答案 2 :(得分:0)

  1. 构建决策图,为其添加起始顶点。每个顶点包含“修剪级别”,即应当将所有数组值减少到当前节点左侧的值。起始顶点的“修剪水平”是无穷大。图的每个边都有一个值,对应于决策成本。
  2. 对于每个数组元素,从最右边开始,执行步骤3 .. 5.
  3. 对于每个叶顶点,执行步骤4 .. 5.
  4. 创建最多2个传出边,(1)以及删除数组元素的成本和(2)修剪左边所有元素的成本(确切地说,降低“修剪级别”的成本)。
  5. 将这些边连接到新创建的顶点,每个数组元素的一个顶点和每个“修剪级别”。
  6. 找到从起始顶点到其中一个顶点的最短路径,对应于最左边的数组元素。此路径的长度等于解决方案的成本。
  7. 根据决策图减少和删除数组元素。
  8. 该算法可视为蛮力方法的优化。对于暴力搜索,从最右边的数组元素开始,构造二元决策树。每个顶点有2个输出边,一个用于“删除”决定,另一个用于“修剪”决定。决策成本与每个边缘相关联。 “修剪水平”与每个顶点相关联。最佳解决方案由该树中的最短路径确定。

    删除所有路径,这显然不是最佳路径。例如,如果最大元素是数组中的最后一个,则“trim”决策的成本为零,“delete”决策不是最优的。删除路径,从此“删除”决定开始。在此优化之后,决策树更稀疏:一些顶点有2个外边,有些 - 只有一个。

    在每个深度级别,决策树可能有几个具有相同“修剪级别”的顶点。从这些顶点开始的子树彼此相同。这是将所有这些顶点连接到一个顶点的好理由。这会将树转换为最多具有n 2 / 2个顶点的图形。

    <强>复杂性

    该算法的最简单实现是O(n 3 ),因为对于每个O(n 2 )顶点,它在O(n)中迭代地计算修整成本)时间。

    如果有足够的内存来存储所有部分修整成本结果,则无需重复修整成本计算。这可能需要O(n 2 )或甚至O(n)空间。

    通过这种优化,该算法为O(n 2 )。由于图的结构简单,最短路径搜索具有O(n 2 )复杂度,而不是O(n 2 * log(n))。

    C ++ 11实现(空间和时间复杂度均为O(n 2 )):

    //g++ -std=c++0x
    #include <iostream>
    #include <vector>
    #include <algorithm>
    
    typedef unsigned val_t;
    typedef unsigned long long acc_t; // to avoid overflows
    typedef unsigned ind_t;
    typedef std::vector<val_t> arr_t;
    
    struct Node
    {
      acc_t trimCost;
      acc_t cost;
      ind_t link;
      bool used;
    
      Node()
      : trimCost(0)
      , used(false)
      {}
    };
    
    class Matrix
    {
      std::vector<Node> m;
      ind_t columns;
    
      public:
      Matrix(ind_t rows, ind_t cols)
      : m(rows * cols)
      , columns(cols)
      {}
    
      Node& operator () (ind_t row, ind_t column)
      {
        return m[columns * row + column];
      }
    };
    
    void fillTrimCosts(const arr_t& array, const arr_t& levels, Matrix& matrix)
    {
      for (ind_t row = 0; row != array.size(); ++row)
      {
        for (ind_t column = 0; column != levels.size(); ++column)
        {
          Node& node = matrix(row + 1, column);
          node.trimCost = matrix(row, column).trimCost;
    
          if (array[row] > levels[column])
          {
            node.trimCost += array[row] - levels[column];
          }
        }
      }
    }
    
    void updateNode(Node& node, acc_t cost, ind_t column)
    {
      if (!node.used || node.cost > cost)
      {
        node.cost = cost;
        node.link = column;
      }
    }
    
    acc_t transform(arr_t& array)
    {
      const ind_t size = array.size();
    
      // Sorted array of trim levels
      arr_t levels = array;
      std::sort(levels.begin(), levels.end());
      levels.erase(
        std::unique(levels.begin(), levels.end()),
        levels.end());
    
      // Initialize matrix
      Matrix matrix(size + 1, levels.size());
      fillTrimCosts(array, levels, matrix);
      Node& startNode = matrix(size, levels.size() - 1);
      startNode.used = true;
      startNode.cost = 0;
    
      // For each array element, starting from the last one
      for (ind_t row = size; row != 0; --row)
      {
        // Determine trim level for this array element
        auto iter = std::lower_bound(levels.begin(), levels.end(), array[row - 1]);
        const ind_t newLevel = iter - levels.begin();
    
        // For each trim level
        for (ind_t column = 0; column != levels.size(); ++column)
        {
          const Node& node = matrix(row, column);
          if (!node.used)
            continue;
    
          // Determine cost of trimming to current array element's level
          const acc_t oldCost = node.trimCost;
          const acc_t newCost = matrix(row, newLevel).trimCost;
          const acc_t trimCost = (newCost > oldCost)? newCost - oldCost: 0;
    
          // Nodes for "trim" and "delete" decisions
          Node& trimNode = matrix(row - 1, newLevel);
          Node& nextNode = matrix(row - 1, column);
    
          if (trimCost)
          {
            // Decision needed, update both nodes
            updateNode(trimNode, trimCost + node.cost, column);
            updateNode(nextNode, array[row - 1] + node.cost, column);
            trimNode.used = true;
          }
          else
          {
            // No decision needed, pass current state to the next row's node
            updateNode(nextNode, node.cost, column);
          }
    
          nextNode.used = true;
        }
      }
    
      // Find optimal cost and starting trim level for it
      acc_t bestCost = size * levels.size();
      ind_t bestLevel = levels.size();
      for (ind_t column = 0; column != levels.size(); ++column)
      {
        const Node& node = matrix(0, column);
        if (node.used && node.cost < bestCost)
        {
          bestCost = node.cost;
          bestLevel = column;
        }
      }
    
      // Trace the path of minimum cost
      for (ind_t row = 0; row != size; ++row)
      {
        const Node& node = matrix(row, bestLevel);
        const ind_t next = node.link;
    
        if (next == bestLevel && node.cost != matrix(row + 1, next).cost)
        {
          array[row] = 0;
        }
        else if (array[row] > levels[bestLevel])
        {
          array[row] = levels[bestLevel];
        }
    
        bestLevel = next;
      }
    
      return bestCost;
    }
    
    void printArray(const arr_t& array)
    {
      for (val_t val: array)
        if (val)
          std::cout << val << ' ';
        else
          std::cout << "* ";
      std::cout << std::endl;
    }
    
    int main()
    {
      arr_t array({9,8,7,6,5,4,3,2,1});
      printArray(array);
    
      acc_t cost = transform(array);
      printArray(array);
      std::cout << "Cost=" << cost << std::endl;
    
      return 0;
    }